From ffe4f04a018509d23dfba85d5eb052f71c9ff7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=95=85=E6=99=9A?= Date: Thu, 17 Jul 2025 18:40:51 +0800 Subject: [PATCH 001/239] PullRequest: 894 feat: add warning text in schedule task page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/04 of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/894 Reviewed-by: 晓康 * feat: add warning text in schedule task page --- .../Task/DataArchiveTask/CreateModal/index.tsx | 18 +++++++++++------- .../Task/DataClearTask/CreateModal/index.tsx | 18 +++++++++++------- .../Task/PartitionTask/CreateModal/index.tsx | 2 ++ .../Task/SQLPlanTask/CreateModal/index.tsx | 2 ++ .../Task/component/ExecuteFailTip/index.tsx | 14 ++++++++++++++ .../Task/component/TaskTable/index.tsx | 12 +++++++++++- 6 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 src/component/Task/component/ExecuteFailTip/index.tsx diff --git a/src/component/Task/DataArchiveTask/CreateModal/index.tsx b/src/component/Task/DataArchiveTask/CreateModal/index.tsx index fc64f4efd..8f39ee51c 100644 --- a/src/component/Task/DataArchiveTask/CreateModal/index.tsx +++ b/src/component/Task/DataArchiveTask/CreateModal/index.tsx @@ -72,6 +72,7 @@ import ShardingStrategyItem from '../../component/ShardingStrategyItem'; import { disabledDate, disabledTime } from '@/util/utils'; import DirtyRowAction from '../../component/DirtyRowAction'; import MaxAllowedDirtyRowCount from '../../component/MaxAllowedDirtyRowCount'; +import ExecuteFailTip from '../../component/ExecuteFailTip'; export enum IArchiveRange { PORTION = 'portion', @@ -731,13 +732,16 @@ const CreateModal: React.FC = (props) => { } if (triggerStrategy === TaskExecStrategy.TIMER) { return ( - - - + <> + + + + + ); } return null; diff --git a/src/component/Task/DataClearTask/CreateModal/index.tsx b/src/component/Task/DataClearTask/CreateModal/index.tsx index d12e21d6e..2413a8277 100644 --- a/src/component/Task/DataClearTask/CreateModal/index.tsx +++ b/src/component/Task/DataClearTask/CreateModal/index.tsx @@ -58,6 +58,7 @@ import { disabledDate, disabledTime } from '@/util/utils'; import { useRequest } from 'ahooks'; import DirtyRowAction from '../../component/DirtyRowAction'; import MaxAllowedDirtyRowCount from '../../component/MaxAllowedDirtyRowCount'; +import ExecuteFailTip from '../../component/ExecuteFailTip'; export enum IArchiveRange { PORTION = 'portion', @@ -617,13 +618,16 @@ const CreateModal: React.FC = (props) => { } if (triggerStrategy === TaskExecStrategy.TIMER) { return ( - - - + <> + + + + + ); } return null; diff --git a/src/component/Task/PartitionTask/CreateModal/index.tsx b/src/component/Task/PartitionTask/CreateModal/index.tsx index 5a29f3e77..2b437bf0a 100644 --- a/src/component/Task/PartitionTask/CreateModal/index.tsx +++ b/src/component/Task/PartitionTask/CreateModal/index.tsx @@ -67,6 +67,7 @@ import { import styles from './index.less'; import { useRequest } from 'ahooks'; import DescriptionInput from '../../component/DescriptionInput'; +import ExecuteFailTip from '../../component/ExecuteFailTip'; const { Paragraph, Text } = Typography; @@ -722,6 +723,7 @@ const CreateModal: React.FC = inject('modalStore')( + = (props) => { + diff --git a/src/component/Task/component/ExecuteFailTip/index.tsx b/src/component/Task/component/ExecuteFailTip/index.tsx new file mode 100644 index 000000000..c95e2c4fa --- /dev/null +++ b/src/component/Task/component/ExecuteFailTip/index.tsx @@ -0,0 +1,14 @@ +import { Alert } from 'antd'; + +export default function ExecuteFailTip() { + return ( + + ); +} diff --git a/src/component/Task/component/TaskTable/index.tsx b/src/component/Task/component/TaskTable/index.tsx index cd8aaba62..59ff2f181 100644 --- a/src/component/Task/component/TaskTable/index.tsx +++ b/src/component/Task/component/TaskTable/index.tsx @@ -44,7 +44,7 @@ import { useLoop } from '@/util/hooks/useLoop'; import { formatMessage } from '@/util/intl'; import { getLocalFormatDateTime } from '@/util/utils'; import { DownOutlined, SearchOutlined } from '@ant-design/icons'; -import { Button, DatePicker, Tooltip, Popover, Space, Typography } from 'antd'; +import { Button, DatePicker, Tooltip, Popover, Space, Typography, Alert } from 'antd'; import { flatten } from 'lodash'; import { inject, observer } from 'mobx-react'; import type { Dayjs } from 'dayjs'; @@ -760,6 +760,16 @@ const TaskTable: React.FC = inject( return ( <> + {login.isPrivateSpace() && isScheduleMigrateTask(taskTabType as any) && ( + + )} Date: Thu, 24 Jul 2025 10:45:32 +0800 Subject: [PATCH 002/239] =?UTF-8?q?PullRequest:=20905=202504=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/2504 of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/905 Reviewed-by: 晓康 * feat: update import task table * fix: fixes dayjs transform time error when offset is float * feat: support database changes * feat: support import in private space * feat: add manageLinkVisible params in sessionDropdown * feat: add imported descriptions --- .../CreateModal/index.tsx | 23 +- .../DetailContent/index.tsx | 3 +- .../CreateModal/index.tsx | 23 +- .../DetailContent/index.tsx | 3 +- .../Task/DataMockerTask/CreateModal/form.tsx | 2 +- .../CreateModal/index.tsx | 4 +- .../Task/component/DatabaseSelect/index.tsx | 20 +- .../ImportModal/DatabaseChangeItem.tsx | 51 ++ .../ImportModal/DatabaseInfoPopover.tsx | 116 ++++ .../ImportModal/ImportPreviewTable.tsx | 569 ++++++++---------- .../Task/component/ImportModal/index.less | 25 + .../Task/component/ImportModal/index.tsx | 269 +++++---- .../Task/component/ImportModal/useColumn.tsx | 248 ++++++++ .../Task/component/ImportModal/useImport.tsx | 58 +- .../Task/component/TaskTable/index.tsx | 4 +- src/component/Task/helper.tsx | 44 ++ src/d.ts/importTask.ts | 28 +- .../ManageModal/Database/CreateAuth/index.tsx | 2 +- .../Database/TaskApplyList/index.tsx | 2 +- .../Database/UserAuthList/index.tsx | 2 +- .../ManageModal/Table/CreateAuth/index.tsx | 3 +- .../ManageModal/Table/TaskApplyList/index.tsx | 2 +- .../ManageModal/Table/UserAuthList/index.tsx | 2 +- .../SessionSelect/SelectItem.tsx | 31 +- .../SessionSelect/SessionDropdown/index.tsx | 115 ++-- src/plugins/defaultConfig.ts | 6 +- src/svgr/source_database.svg | 7 + src/svgr/target_database.svg | 7 + 28 files changed, 1102 insertions(+), 567 deletions(-) create mode 100644 src/component/Task/component/ImportModal/DatabaseChangeItem.tsx create mode 100644 src/component/Task/component/ImportModal/DatabaseInfoPopover.tsx create mode 100644 src/component/Task/component/ImportModal/useColumn.tsx create mode 100644 src/svgr/source_database.svg create mode 100644 src/svgr/target_database.svg diff --git a/src/component/Task/ApplyDatabasePermission/CreateModal/index.tsx b/src/component/Task/ApplyDatabasePermission/CreateModal/index.tsx index 803bd628e..f5f012d67 100644 --- a/src/component/Task/ApplyDatabasePermission/CreateModal/index.tsx +++ b/src/component/Task/ApplyDatabasePermission/CreateModal/index.tsx @@ -42,36 +42,15 @@ import dayjs from 'dayjs'; import React, { useEffect, useState } from 'react'; import styles from './index.less'; import ProjectSelectEmpty from '@/component/Empty/ProjectSelectEmpty'; +import { getExpireTime } from '../../helper'; const CheckboxGroup = Checkbox.Group; -const MAX_DATE = '9999-12-31 23:59:59'; -const MAX_DATE_LABEL = '9999-12-31'; - const defaultValue = { databases: [], expireTime: '7,days', }; -export const getExpireTime = (expireTime, customExpireTime, isCustomExpireTime) => { - if (isCustomExpireTime) { - return customExpireTime?.valueOf(); - } else { - const [offset, unit] = expireTime.split(',') ?? []; - return offset === 'never' ? dayjs(MAX_DATE)?.valueOf() : dayjs().add(offset, unit)?.valueOf(); - } -}; - -export const getExpireTimeLabel = (expireTime) => { - const label = dayjs(expireTime).format('YYYY-MM-DD'); - return label === MAX_DATE_LABEL - ? formatMessage({ - id: 'src.component.Task.ApplyDatabasePermission.CreateModal.B5C7760D', - defaultMessage: '永不过期', - }) - : label; -}; - const Label: React.FC<{ text: string; docKey: string; diff --git a/src/component/Task/ApplyDatabasePermission/DetailContent/index.tsx b/src/component/Task/ApplyDatabasePermission/DetailContent/index.tsx index bcccfc137..58bb3ddb7 100644 --- a/src/component/Task/ApplyDatabasePermission/DetailContent/index.tsx +++ b/src/component/Task/ApplyDatabasePermission/DetailContent/index.tsx @@ -24,7 +24,8 @@ import { } from '@/d.ts'; import { getFormatDateTime } from '@/util/utils'; import { Descriptions, Divider, Alert, Space } from 'antd'; -import { getExpireTimeLabel, permissionOptionsMap } from '../'; +import { permissionOptionsMap } from '../'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import styles from './index.less'; import { DBType, IDatabase } from '@/d.ts/database'; import DatabaseIcon from '@/component/StatusIcon/DatabaseIcon'; diff --git a/src/component/Task/ApplyTablePermission/CreateModal/index.tsx b/src/component/Task/ApplyTablePermission/CreateModal/index.tsx index 956090815..1f40803b5 100644 --- a/src/component/Task/ApplyTablePermission/CreateModal/index.tsx +++ b/src/component/Task/ApplyTablePermission/CreateModal/index.tsx @@ -46,36 +46,15 @@ import { inject, observer } from 'mobx-react'; import dayjs from 'dayjs'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import styles from './index.less'; +import { getExpireTime } from '../../helper'; const CheckboxGroup = Checkbox.Group; -const MAX_DATE = '9999-12-31 23:59:59'; -const MAX_DATE_LABEL = '9999-12-31'; - const defaultValue = { tables: [], expireTime: '7,days', }; -export const getExpireTime = (expireTime, customExpireTime, isCustomExpireTime) => { - if (isCustomExpireTime) { - return customExpireTime?.valueOf(); - } else { - const [offset, unit] = expireTime.split(',') ?? []; - return offset === 'never' ? dayjs(MAX_DATE)?.valueOf() : dayjs().add(offset, unit)?.valueOf(); - } -}; - -export const getExpireTimeLabel = (expireTime) => { - const label = dayjs(expireTime).format('YYYY-MM-DD'); - return label === MAX_DATE_LABEL - ? formatMessage({ - id: 'src.component.Task.ApplyTablePermission.CreateModal.BC4488C7', - defaultMessage: '永不过期', - }) - : label; -}; - const Label: React.FC<{ text: string; docKey: string; diff --git a/src/component/Task/ApplyTablePermission/DetailContent/index.tsx b/src/component/Task/ApplyTablePermission/DetailContent/index.tsx index 86741d510..79d6caef0 100644 --- a/src/component/Task/ApplyTablePermission/DetailContent/index.tsx +++ b/src/component/Task/ApplyTablePermission/DetailContent/index.tsx @@ -22,7 +22,8 @@ import type { IApplyTablePermissionTaskParams, TaskDetail } from '@/d.ts'; import { getFormatDateTime } from '@/util/utils'; import { Descriptions, Divider, Space } from 'antd'; import { useMemo } from 'react'; -import { getExpireTimeLabel, permissionOptionsMap } from '../'; +import { permissionOptionsMap } from '../'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import RiskLevelLabel from '@/component/RiskLevelLabel'; const getConnectionColumns = () => { diff --git a/src/component/Task/DataMockerTask/CreateModal/form.tsx b/src/component/Task/DataMockerTask/CreateModal/form.tsx index c6d450f43..03bf9436c 100644 --- a/src/component/Task/DataMockerTask/CreateModal/form.tsx +++ b/src/component/Task/DataMockerTask/CreateModal/form.tsx @@ -201,7 +201,7 @@ const DataMockerForm: React.FC = inject('settingStore')( form.resetFields(['tableName', 'columns'])} projectId={projectId} - width={'441px'} + width={441} type={TaskType.DATAMOCK} /> diff --git a/src/component/Task/StructureComparisonTask/CreateModal/index.tsx b/src/component/Task/StructureComparisonTask/CreateModal/index.tsx index 6e00ab443..df9069cf5 100644 --- a/src/component/Task/StructureComparisonTask/CreateModal/index.tsx +++ b/src/component/Task/StructureComparisonTask/CreateModal/index.tsx @@ -195,7 +195,7 @@ const StructureComparisonTask: React.FC = ({ projectId, modalStore }) => = ({ projectId, modalStore }) => /> void; + showProject?: boolean; + validateStatus?: 'warning' | 'error' | 'success' | 'validating' | undefined; + help?: string; + style?: React.CSSProperties; + popoverWidth?: number; + manageLinkVisible?: boolean; } const DatabaseSelect: React.FC = (props) => { const { @@ -53,6 +59,12 @@ const DatabaseSelect: React.FC = (props) => { disabled = false, isLogicalDatabase = false, onChange, + showProject = true, + validateStatus, + help, + style, + popoverWidth, + manageLinkVisible = false, } = props; return ( @@ -69,6 +81,9 @@ const DatabaseSelect: React.FC = (props) => { }), //请选择数据库 }, ]} + validateStatus={validateStatus} + help={help} + style={style} > = (props) => { onChange={onChange} isLogicalDatabase={isLogicalDatabase} placeholder={placeholder} + showProject={showProject} + popoverWidth={popoverWidth} + manageLinkVisible={manageLinkVisible} /> ); diff --git a/src/component/Task/component/ImportModal/DatabaseChangeItem.tsx b/src/component/Task/component/ImportModal/DatabaseChangeItem.tsx new file mode 100644 index 000000000..9f2449e07 --- /dev/null +++ b/src/component/Task/component/ImportModal/DatabaseChangeItem.tsx @@ -0,0 +1,51 @@ +import { Form } from 'antd'; +import DatabaseSelect from '../DatabaseSelect'; +import { TaskType } from '@/d.ts'; +import styles from './index.less'; +import { useState } from 'react'; + +const DatabaseChangeItem = ({ + defaultDatabaseId, + taskType, + projectId, + onChange, +}: { + defaultDatabaseId: number; + taskType: TaskType; + projectId: number; + onChange?: (databaseId: number) => void; +}) => { + const [form] = Form.useForm(); + const [isChanged, setIsChanged] = useState(false); + + return ( +
{ + onChange?.(values.databaseId); + setIsChanged(true); + }} + className={isChanged ? styles.changeDatabaseInput : ''} + > + + + ); +}; + +export default DatabaseChangeItem; diff --git a/src/component/Task/component/ImportModal/DatabaseInfoPopover.tsx b/src/component/Task/component/ImportModal/DatabaseInfoPopover.tsx new file mode 100644 index 000000000..e739423d2 --- /dev/null +++ b/src/component/Task/component/ImportModal/DatabaseInfoPopover.tsx @@ -0,0 +1,116 @@ +import { IImportDatabaseView } from '@/d.ts/importTask'; +import { formatMessage } from '@/util/intl'; +import Icon, { InfoCircleOutlined } from '@ant-design/icons'; +import { getDataSourceStyleByConnectType } from '@/common/datasource'; +import { getCloudProviderName } from '../AsyncTaskOperationButton/helper'; +import { fromODCPRoviderToProvider } from '@/d.ts/migrateTask'; +import { ConnectTypeText } from '@/constant/label'; +import { + Tooltip, + Popover, + Table, + Descriptions, + Typography, + Empty, + Radio, + Space, + Checkbox, + Flex, + Tag, +} from 'antd'; + +const DatabaseInfoPopover = ({ + title, + value, + popoverWidth, + children, +}: { + title: string; + value: IImportDatabaseView; + popoverWidth: number; + children: React.JSX.Element; +}) => { + const items = [ + { + key: 'datasource', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.A8BC98CA', + defaultMessage: '数据源', + }), + children: value?.name, + }, + { + key: 'databaseName', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.0B1F9DCD', + defaultMessage: '数据库', + }), + children: value?.databaseName, + }, + { + key: 'type', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.263EFA83', + defaultMessage: '类型', + }), + children: ( +
+ + + {ConnectTypeText(value?.type)} +
+ ), + }, + { + key: 'host', + label: '主机 IP/域名:', + children: {value?.host}, + }, + { + key: 'host', + label: '端口', + children: {value?.port}, + }, + { + key: 'username', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.5928CAE8', + defaultMessage: '数据库账号', + }), + children: value?.username || '-', + }, + ]; + + return ( + +

{title}

+ + {items?.map((i) => { + return ( + + {i?.children} + + ); + })} + + + ) : null + } + arrow={false} + > +
{children}
+
+ ); +}; + +export default DatabaseInfoPopover; diff --git a/src/component/Task/component/ImportModal/ImportPreviewTable.tsx b/src/component/Task/component/ImportModal/ImportPreviewTable.tsx index 30865dc3f..a5938d915 100644 --- a/src/component/Task/component/ImportModal/ImportPreviewTable.tsx +++ b/src/component/Task/component/ImportModal/ImportPreviewTable.tsx @@ -1,362 +1,291 @@ import { formatMessage } from '@/util/intl'; -import React, { useMemo, useState, useEffect } from 'react'; -import { IDatasourceInfo } from '.'; +import React, { useMemo, useState, useEffect, useCallback } from 'react'; +import { IDatasourceInfo, IMPORTABLE_TYPE } from '.'; import { IImportDatabaseView, IImportScheduleTaskView, ScheduleNonImportableType, ScheduleNonImportableTypeMap, } from '@/d.ts/importTask'; -import { Tooltip, Popover, Table, Descriptions, Typography, Empty, Radio } from 'antd'; -import type { ColumnsType } from 'antd/es/table'; -import { TaskType } from '@/d.ts'; -import { ConnectTypeText } from '@/constant/label'; -import Icon from '@ant-design/icons'; -import { getDataSourceStyleByConnectType } from '@/common/datasource'; -import { getCloudProviderName } from '../AsyncTaskOperationButton/helper'; -import { fromODCPRoviderToProvider } from '@/d.ts/migrateTask'; +import { Tooltip, Table, Typography, Empty, Radio, Checkbox, Flex } from 'antd'; +import { TaskType, ConnectType } from '@/d.ts'; +import { useColumns } from './useColumn'; interface ImportPreviewTableProps { data: IImportScheduleTaskView[]; loading?: boolean; datasourceInfo: IDatasourceInfo; taskType: TaskType; + projectId: number; + selectedRowKeys: string[]; + setSelectedRowKeys: (selectedRowKeys: string[]) => void; + databaseSelections: Record< + string, + { databaseId: number | null; targetDatabaseId: number | null } + >; + setDatabaseSelections: React.Dispatch< + React.SetStateAction< + Record + > + >; } -const ImportPreviewTable: React.FC = ({ data, loading, taskType }) => { - const [tableType, setTableType] = useState( - 'importable', - ); - - const DatabaseInfoPopover = ({ - title, - value, - width, - children, - }: { - title: string; - value: IImportDatabaseView; - width: number; - children: React.JSX.Element; - }) => { - const items = [ - { - key: 'datasource', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.A8BC98CA', - defaultMessage: '数据源', - }), - children: value?.name, - }, - { - key: 'databaseName', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.0B1F9DCD', - defaultMessage: '数据库', - }), - children: value?.databaseName, - }, - { - key: 'type', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.263EFA83', - defaultMessage: '类型', - }), - children: ( -
- - - {ConnectTypeText(value?.type)} -
- ), - }, - { - key: 'cloudProvider', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.40440087', - defaultMessage: '云厂商', - }), - children: ( -
- {getCloudProviderName(fromODCPRoviderToProvider[value?.cloudProvider]) || '-'} -
- ), - }, - { - key: 'region', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.F2C95E45', - defaultMessage: '地域', - }), - children:
{value?.region || '-'}
, - }, - { - key: 'host', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.EFE4E0D0', - defaultMessage: '连接信息', - }), - children: ( - - {value?.host}:{value?.port} - - ), - }, - { - key: 'instanceNickName', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.4A911A47', - defaultMessage: '实例名称', - }), - children: value?.instanceNickName || value?.instanceId || '-', - }, - { - key: 'tenantNickName', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.396EF2AD', - defaultMessage: '租户名称', - }), - children: value?.tenantNickName || value?.tenantId || '-', - }, - { - key: 'username', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.5928CAE8', - defaultMessage: '数据库账号', - }), - children: value?.username || '-', - }, - ]; - - return ( - -

{title}

- - {items?.map((i) => { - return ( - - {i?.children} - - ); - })} - - - ) : null - } - > -
-
{children}
-
-
- ); - }; - - const dlmColumns = [ - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.B4E8467F', - defaultMessage: '源端数据库', - }), - dataIndex: 'databaseView', - key: 'databaseView', - render: (_: IImportDatabaseView) => { - if (!_) return '-'; - return ( - - - {_ ? `${_?.name} / ${_?.databaseName}` : '-'} - - - ); - }, - width: 200, - ellipsis: true, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.76F572C8', - defaultMessage: '目标端数据库', - }), - dataIndex: 'targetDatabaseView', - key: 'targetDatabaseView', - render: (_: IImportDatabaseView) => { - if (!_) return '-'; - return ( - - - {_ ? `${_?.name} / ${_?.databaseName}` : '-'} - - - ); - }, - width: 200, - ellipsis: true, - }, - ]; - - const otherScheduleColumns = [ - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.B5C62BDC', - defaultMessage: '工单描述', - }), - dataIndex: 'description', - key: 'description', - width: 340, - render: (_, record) => { - return _ || '-'; - }, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.1EB75659', - defaultMessage: '数据库', - }), - dataIndex: 'databaseView', - key: 'databaseView', - render: (_: IImportDatabaseView) => { - console.log('databaseView', _); - if (!_) return '-'; - return ( - - - {_ ? `${_?.name} / ${_?.databaseName}` : '-'} - - - ); - }, - width: 200, - ellipsis: true, - }, - ]; - - const columns: ColumnsType = [ - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.5DE578E8', - defaultMessage: '原编号', - }), - dataIndex: 'originId', - key: 'originId', - width: 100, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.2043ADA9', - defaultMessage: '原项目', - }), - dataIndex: 'originProjectName', - key: 'originProjectName', - width: 100, - render: (_) => { - return _ || '-'; - }, - }, - // 数据清理/归档有源端目标端, 分区计划和sql计划只有数据库 - ...([TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE]?.includes(taskType) ? dlmColumns : []), - ...([TaskType.SQL_PLAN, TaskType.PARTITION_PLAN]?.includes(taskType) - ? otherScheduleColumns - : []), - ]?.filter(Boolean); - +const ImportPreviewTable: React.FC = ({ + loading, + taskType, + projectId, + data, + selectedRowKeys, + setSelectedRowKeys, + databaseSelections, + setDatabaseSelections, +}) => { + const [showOnlyImportable, setShowOnlyImportable] = useState(false); const groupedData = useMemo(() => { return data.reduce( ( - acc: Record, - + acc: Record, item, ) => { - const groupKey = item.importable ? 'importable' : item.nonImportableType; + let groupKey: ScheduleNonImportableType | 'TO_BE_IMPORTED'; + + if ( + item.importable || + item.nonImportableType === ScheduleNonImportableType.DATASOURCE_NON_EXIST || + item.nonImportableType === ScheduleNonImportableType.LACK_OF_INSTANCE + ) { + groupKey = 'TO_BE_IMPORTED'; + } else { + groupKey = item.nonImportableType; + } + acc[groupKey] = acc[groupKey] || []; acc[groupKey].push(item); return acc; }, - {} as Record, + {} as Record, ); }, [data]); - // 当数据变化时,如果当前tableType不存在则重置为'importable' + const [tableType, setTableType] = useState( + groupedData?.['TO_BE_IMPORTED']?.length > 0 + ? 'TO_BE_IMPORTED' + : Object.values(ScheduleNonImportableType).find((key) => groupedData?.[key]?.length > 0) || + 'TO_BE_IMPORTED', + ); + // 检查一个工单是否已经选择了所需的数据库 + const hasSelectedAllDatabases = useCallback( + (item: IImportScheduleTaskView) => { + const hasSourceDatabase = + item.databaseView?.matchedDatabaseId || databaseSelections[item.originId]?.databaseId; + const hasTargetDatabase = + item.targetDatabaseView?.matchedDatabaseId || + databaseSelections[item.originId]?.targetDatabaseId || + !item?.targetDatabaseView; + + // 如果是数据清理/归档任务,需要检查源端和目标端 + if ([TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE].includes(taskType)) { + return hasSourceDatabase && hasTargetDatabase; + } + // 其他任务类型只需要检查源端 + return hasSourceDatabase; + }, + [databaseSelections, taskType], + ); + + // 判断工单是否应该被选中 + const shouldBeSelected = useCallback( + (item: IImportScheduleTaskView) => { + if (item.importable) return true; + + if ( + item.nonImportableType === ScheduleNonImportableType.DATASOURCE_NON_EXIST || + item.nonImportableType === ScheduleNonImportableType.LACK_OF_INSTANCE + ) { + return hasSelectedAllDatabases(item); + } + + return false; + }, + [hasSelectedAllDatabases], + ); + + // 更新选中状态 + const updateSelectedRowKeys = useCallback(() => { + if (!data?.length) return; + + const selectedIds = data.filter((item) => shouldBeSelected(item)).map((item) => item.originId); + + setSelectedRowKeys(selectedIds); + }, [data, shouldBeSelected, setSelectedRowKeys]); + + // 初始化和数据变化时更新选中状态 useEffect(() => { - if (data?.length > 0 && groupedData[tableType] === undefined) { - setTableType('importable'); - } - }, [data]); + updateSelectedRowKeys(); + }, [data, updateSelectedRowKeys]); + + // 数据库选择变化时更新选中状态 + useEffect(() => { + updateSelectedRowKeys(); + }, [databaseSelections, updateSelectedRowKeys]); + + const handleDatabaseChange = useCallback( + (originId: string, type: 'databaseId' | 'targetDatabaseId', databaseId: number) => { + setDatabaseSelections((prev) => ({ + ...prev, + [originId]: { + databaseId: type === 'databaseId' ? databaseId : prev[originId]?.databaseId ?? null, + targetDatabaseId: + type === 'targetDatabaseId' ? databaseId : prev[originId]?.targetDatabaseId ?? null, + }, + })); + }, + [], + ); + + const { importableColumns, typeNotMatchColumns, alreadyExistColumns } = useColumns( + taskType, + projectId, + handleDatabaseChange, + ); + + const handleShowOnlyImportableChange = useCallback( + (checked: boolean) => { + setShowOnlyImportable(checked); + if (checked) { + updateSelectedRowKeys(); + } + }, + [updateSelectedRowKeys], + ); + + const getFilteredData = useCallback( + (data: IImportScheduleTaskView[]) => { + if (!showOnlyImportable) { + return data; + } + return data.filter(shouldBeSelected); + }, + [showOnlyImportable, shouldBeSelected], + ); const tableRender = () => { - if (tableType === 'importable' && !groupedData['importable']) { - return ( - - ); - } return ( <> - +
+ {!groupedData['TO_BE_IMPORTED'] ? ( + + ) : ( +
{ + setSelectedRowKeys(selectedRowKeys as string[]); + }, + getCheckboxProps: (record) => ({ + disabled: !( + record.importable || + ((record.nonImportableType === ScheduleNonImportableType.DATASOURCE_NON_EXIST || + record.nonImportableType === ScheduleNonImportableType.LACK_OF_INSTANCE) && + hasSelectedAllDatabases(record)) + ), + }), + }} + /> + )} + +
+
+ +
+
+ ); }; + const tablePrefixRender = (type: ScheduleNonImportableType | 'TO_BE_IMPORTED') => { + const map = { + TO_BE_IMPORTED: ( + + 勾选需要导入的工单,导入后将重新启用。导入前请检查涉及的新旧数据库对象是否一致,否则导入或执行时可能出现失败。 + handleShowOnlyImportableChange(e.target.checked)} + > + 仅显示已选择数据库的工单 + + + ), + [ScheduleNonImportableType.IMPORTED]: ( +
以下工单已导入,无需重复操作。
+ ), + [ScheduleNonImportableType.TYPE_NOT_MATCH]: ( +
+ 以下工单类型不匹配、无法导入,建议选择对应工单类型重新导入。 +
+ ), + }; + return map[type]; + }; + return ( <> - {groupedData['importable']?.length !== data?.length ? ( - setTableType(e.target.value)} - style={{ marginBottom: 16 }} - > - - {formatMessage({ - id: 'src.component.Task.component.ImportModal.E8DE787E', - defaultMessage: '可导入', - })} - + setTableType(e.target.value)} + style={{ marginBottom: 16 }} + > + {groupedData['TO_BE_IMPORTED']?.length > 0 && ( + + 待导入 - {groupedData['importable']?.length || 0} + {groupedData['TO_BE_IMPORTED']?.length || 0} - {Object.keys(ScheduleNonImportableType)?.map((key) => { + )} + {Object.keys(ScheduleNonImportableType) + ?.filter( + (key) => + key !== ScheduleNonImportableType.DATASOURCE_NON_EXIST && + key !== ScheduleNonImportableType.LACK_OF_INSTANCE && + groupedData[key as ScheduleNonImportableType]?.length > 0, + ) + ?.map((key) => { return ( {ScheduleNonImportableTypeMap[key as ScheduleNonImportableType]}{' '} @@ -370,8 +299,8 @@ const ImportPreviewTable: React.FC = ({ data, loading, ); })} - - ) : null} + + {tablePrefixRender(tableType)} {tableRender()} ); diff --git a/src/component/Task/component/ImportModal/index.less b/src/component/Task/component/ImportModal/index.less index 76b87d39a..1f1744501 100644 --- a/src/component/Task/component/ImportModal/index.less +++ b/src/component/Task/component/ImportModal/index.less @@ -9,3 +9,28 @@ } } } + +.checkboxError { + color: var(--text-color-error); + :global(.ant-checkbox-inner) { + border-color: var(--text-color-error); + color: var(--text-color-error); + } +} + +.changeDatabaseInput { + :global(.ant-form-item) { + :global(.ant-select) { + :global(.ant-select-selector) { + background-color: var(--table-edit-color); + } + } + } +} + +.databasePopoverName { + cursor: pointer; + &:hover { + color: var(--text-color-link); + } +} diff --git a/src/component/Task/component/ImportModal/index.tsx b/src/component/Task/component/ImportModal/index.tsx index 55e0c5bdf..271ab6514 100644 --- a/src/component/Task/component/ImportModal/index.tsx +++ b/src/component/Task/component/ImportModal/index.tsx @@ -6,7 +6,19 @@ import { IImportScheduleTaskView, IScheduleTaskImportRequest, } from '@/d.ts/importTask'; -import { Alert, Button, Form, Input, Modal, Select, Space, Spin, Typography } from 'antd'; +import { + Alert, + Button, + Checkbox, + Flex, + Form, + Input, + Modal, + Select, + Space, + Spin, + Typography, +} from 'antd'; import { CheckCircleFilled, CloseCircleFilled, @@ -30,7 +42,9 @@ import CreateProjectDrawer from '@/page/Project/Project/CreateProject/Drawer'; import NewDatasourceButton from '@/page/Datasource/Datasource/NewDatasourceDrawer/NewButton'; import { TaskTypeMap } from '../TaskTable'; import { listProjects } from '@/common/network/project'; +import styles from './index.less'; +export const IMPORTABLE_TYPE = 'IMPORTABLE_TYPE'; interface IImportModalProps { open: boolean; onCancel: () => void; @@ -50,6 +64,7 @@ export interface IDatasourceInfo { } const ImportModal: React.FC = ({ open, onCancel, onOk, taskType }) => { const [form] = Form.useForm(); + const [isConfirm, setIsConfirm] = useState(false); const [scheduleTaskImportRequest, setScheduleTaskImportRequest] = useState(); const [previewData, setPreviewData] = useState([]); @@ -61,6 +76,11 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy matchedList: [], createdList: [], }); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [databaseSelections, setDatabaseSelections] = useState< + Record + >({}); + const [notConfirmButSubmit, setNotConfirmButSubmit] = useState(false); const [uploadStatus, setUploadStatus] = useState('default'); const [addProjectDropVisible, setAddProjectDropVisible] = useState(false); const { data: projects, run: loadProjects } = useRequest(listProjects, { @@ -95,9 +115,6 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy setLoading(false); setScheduleTaskImportRequest({ ...params, - importableExportRowId: (previewResult as IImportScheduleTaskView[]) - ?.map((i) => (i.importable ? i?.exportRowId : null)) - ?.filter(Boolean), }); setPreviewData((previewResult as IImportScheduleTaskView[]) || []); setDatasourceInfo( @@ -185,6 +202,9 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy e?.stopPropagation(); setUploadStatus('default'); form.setFieldValue('importFile', []); + setNotConfirmButSubmit(false); + setDatabaseSelections({}); + setSelectedRowKeys([]); }; const uploadInfoMap = useMemo(() => { @@ -301,33 +321,66 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy ) : ( - - - - + 我己确认导入的工单新旧数据库对象一致 + + + + + + ) } width={step === 'upload' ? 520 : 960} @@ -337,11 +390,26 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy + 仅支持导入由 阿里云 OceanBase 数据研发 或 ODC + 导出的配置文件;在导入之前,请先将添加相关数据源、 井指定对应的项目。 + {}}> + + + + } />
@@ -425,89 +493,72 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy })} /> - - - {formatMessage({ - id: 'src.component.Task.component.ImportModal.B3686BE9', - defaultMessage: '请确认相关数据源已加至项目', - })} - - {}}> - - - - } - > - + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + open={addProjectDropVisible} + onDropdownVisibleChange={setAddProjectDropVisible} + dropdownRender={(menu) => ( + <> + {menu} + { + setAddProjectDropVisible(false)}> + + {formatMessage({ + id: 'src.component.Task.component.ImportModal.68180A60', + defaultMessage: '新建项目', + })} + + } + buttonType="link" + onCreate={() => loadProjects(null, 1, 9999)} + /> + } + + )} + /> + + )} -
+ {step === 'preview' ? ( -
+ ) : null} ); diff --git a/src/component/Task/component/ImportModal/useColumn.tsx b/src/component/Task/component/ImportModal/useColumn.tsx new file mode 100644 index 000000000..aed0995a0 --- /dev/null +++ b/src/component/Task/component/ImportModal/useColumn.tsx @@ -0,0 +1,248 @@ +import { IImportDatabaseView, IImportScheduleTaskView } from '@/d.ts/importTask'; +import { Tooltip, Typography, Flex } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import { TaskStatus, TaskType } from '@/d.ts'; +import Icon, { InfoCircleOutlined } from '@ant-design/icons'; +import StatusLabel from '../Status'; +import DatabaseChangeItem from './DatabaseChangeItem'; +import DatabaseInfoPopover from './DatabaseInfoPopover'; +import { TaskTypeMap } from '../TaskTable'; +import { ReactComponent as SourceDatabase } from '@/svgr/source_database.svg'; +import { ReactComponent as TargetDatabase } from '@/svgr/target_database.svg'; +import styles from './index.less'; + +export const useColumns = ( + taskType: TaskType, + projectId: number, + handleDatabaseChange: ( + originId: string, + type: 'databaseId' | 'targetDatabaseId', + databaseId: number, + ) => void, +) => { + const baseInfoColumns = [ + { + title: '工单', + dataIndex: 'description', + key: 'description', + width: 300, + ellipsis: true, + render: (_, record) => { + return ( + + {_ || '-'} + + #{record?.originId} + · + + {record?.originProjectName} + + + + ); + }, + }, + ]; + + const originDatabaseColumns = [ + { + title: '原数据库', + dataIndex: 'databaseView', + key: 'databaseView', + width: 140, + render: (_: IImportDatabaseView) => { + if (!_) return '-'; + return ( + + + + {_ ? `${_?.databaseName}` : '-'} + + + 数据源: {_?.name} + + + + ); + }, + }, + ]; + + const originDatabaseWithTargetColumns = [ + { + title: '原数据库', + dataIndex: 'databaseId', + key: 'databaseId', + width: 150, + render: (databaseId: number, record: IImportScheduleTaskView) => { + return ( + + + + + + {record.databaseView?.databaseName || '-'} + + + + + + + + {record.targetDatabaseView?.databaseName || '-'} + + + + + ); + }, + }, + ]; + + const originStatusColumns = [ + { + title: '原状态', + dataIndex: 'originStatus', + key: 'originStatus', + width: 120, + render: (status, record) => ( + + + {status !== TaskStatus.ENABLED && ( + + + + )} + + ), + }, + ]; + + const originDatabaseTypeColumns = [ + { + title: '类型', + dataIndex: 'taskType', + key: 'taskType', + width: 120, + render: (status, record) => { + return TaskTypeMap[record.type]; + }, + }, + ]; + + const dlmColumns = [ + ...originDatabaseWithTargetColumns, + { + title: '新源端', + dataIndex: 'databaseView', + key: 'databaseView', + width: 200, + render: (_: IImportDatabaseView, record: IImportScheduleTaskView) => { + if (!_) return '-'; + return ( + + handleDatabaseChange(record.originId, 'databaseId', databaseId) + } + /> + ); + }, + }, + { + title: '新目标端', + dataIndex: 'targetDatabaseView', + key: 'targetDatabaseView', + width: 200, + render: (_: IImportDatabaseView, record: IImportScheduleTaskView) => { + if (!_) return '-'; + return ( + + handleDatabaseChange(record.originId, 'targetDatabaseId', databaseId) + } + /> + ); + }, + }, + ]; + + const otherScheduleColumns = [ + ...originDatabaseColumns, + { + title: '新数据库', + dataIndex: 'databaseView', + key: 'databaseView', + width: 200, + render: (_: IImportDatabaseView, record: IImportScheduleTaskView) => { + return ( + + handleDatabaseChange(record.originId, 'databaseId', databaseId) + } + /> + ); + }, + }, + ]; + + const typeNotMatchColumns = [ + ...baseInfoColumns, + ...originDatabaseColumns, + ...originDatabaseTypeColumns, + ]; + + const alreadyExistColumns = [ + ...baseInfoColumns, + ...([TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE]?.includes(taskType) + ? originDatabaseWithTargetColumns + : []), + ...([TaskType.SQL_PLAN, TaskType.PARTITION_PLAN]?.includes(taskType) + ? originDatabaseColumns + : []), + ]; + + const importableColumns: ColumnsType = [ + ...baseInfoColumns, + ...originStatusColumns, + // 数据清理/归档有源端目标端, 分区计划和sql计划只有数据库 + ...([TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE]?.includes(taskType) ? dlmColumns : []), + ...([TaskType.SQL_PLAN, TaskType.PARTITION_PLAN]?.includes(taskType) + ? otherScheduleColumns + : []), + ]?.filter(Boolean); + + return { + importableColumns, + typeNotMatchColumns, + alreadyExistColumns, + }; +}; diff --git a/src/component/Task/component/ImportModal/useImport.tsx b/src/component/Task/component/ImportModal/useImport.tsx index 8e8da0a66..3a24aefb3 100644 --- a/src/component/Task/component/ImportModal/useImport.tsx +++ b/src/component/Task/component/ImportModal/useImport.tsx @@ -16,6 +16,7 @@ import { useDebounceFn } from 'ahooks'; import React, { useState } from 'react'; import { AsyncTaskType } from '@/d.ts/migrateTask'; import { history } from '@umijs/max'; +import login from '@/store/login'; const downloadLogFromString = (str: string) => { const blob = new Blob([str], { type: 'text/plain' }); @@ -62,20 +63,20 @@ export const useImport = ( downloadLogFromString(res); }; if (result?.every((i) => i.success)) { + const importedCount = result?.filter((i) => i?.remark === 'Have been imported.')?.length; + const importedDescription = importedCount ? `(包含 ${importedCount} 个已导入的任务)` : ''; + const privateSpaceDescription = `${result?.length} 个作业导入成功${importedDescription}。`; notification.success({ message: formatMessage({ id: 'src.component.Task.component.ImportModal.997B6AC7', defaultMessage: '导入定时任务已完成', }), - description: ( + description: login.isPrivateSpace() ? ( + privateSpaceDescription + ) : ( - {formatMessage( - { - id: 'src.component.Task.component.ImportModal.E256F212', - defaultMessage: '{resultLength} 个作业导入成功。 建议手动为任务', - }, - { resultLength: result?.length }, - )} + {`${result?.length} 个作业导入成功${importedDescription}。`} + 建议手动为任务 goNotification(projectId)}> {formatMessage({ id: 'src.component.Task.component.ImportModal.8AD83BC8', @@ -88,26 +89,43 @@ export const useImport = ( })} ), - duration: null, }); } else { const successCount = result?.filter((i) => i?.success)?.length; const failedCount = result?.filter((i) => !i?.success)?.length; + const importedCount = result?.filter( + (i) => i?.success && i?.remark === 'Have been imported.', + )?.length; + const importedDescription = importedCount ? `(包含 ${importedCount} 个已导入的任务)` : ''; + const privateSpaceDescription = ( + + {`${successCount} 个作业导入成功${importedDescription}, ${failedCount} 个作业导入失败。可`} + + {formatMessage({ + id: 'src.component.Task.component.ImportModal.A23B9738', + defaultMessage: '下载日志', + })} + + {formatMessage({ + id: 'src.component.Task.component.ImportModal.997B48CD', + defaultMessage: '查看。', + })} + + ); notification.warning({ message: formatMessage({ - id: 'src.component.Task.component.ImportModal.E12ACFFB', - defaultMessage: '导出定时任务已完成', + id: 'src.component.Task.component.ImportModal.997B6AC7', + defaultMessage: '导入定时任务已完成', }), - description: ( + description: login.isPrivateSpace() ? ( + privateSpaceDescription + ) : ( - {formatMessage( - { - id: 'src.component.Task.component.ImportModal.47DA5967', - defaultMessage: '{successCount} 个作业导入成功,', - }, - { successCount }, - )} + {`${successCount} 个作业导入成功${importedDescription}, `} {formatMessage( { id: 'src.component.Task.component.ImportModal.32698C46', @@ -128,7 +146,6 @@ export const useImport = ( id: 'src.component.Task.component.ImportModal.60663407', defaultMessage: ',保证任务异常能够被及时发现。如需了解导出详情,可', })} - ), - duration: null, }); } diff --git a/src/component/Task/component/TaskTable/index.tsx b/src/component/Task/component/TaskTable/index.tsx index 59ff2f181..575f272ce 100644 --- a/src/component/Task/component/TaskTable/index.tsx +++ b/src/component/Task/component/TaskTable/index.tsx @@ -646,9 +646,7 @@ const TaskTable: React.FC = inject( ]; } return [ - !taskTypeThatCanBeExport.includes(taskTabType) || - login.isPrivateSpace() || - !isSupportTaksImport + !taskTypeThatCanBeExport.includes(taskTabType) || !isSupportTaksImport ? { type: IOperationOptionType.button, content: [ diff --git a/src/component/Task/helper.tsx b/src/component/Task/helper.tsx index 2f4e693af..17d6f4b38 100644 --- a/src/component/Task/helper.tsx +++ b/src/component/Task/helper.tsx @@ -21,6 +21,8 @@ import settingStore from '@/store/setting'; import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; import { flatten } from 'lodash'; +import dayjs from 'dayjs'; + export { TaskTypeMap } from '@/component/Task/component/TaskTable'; // 423 屏蔽 SysFormItem 配置 @@ -318,3 +320,45 @@ export const conditionExpressionColumns = [ }, }, ]; + +type TimeUnit = 'years' | 'months' | 'days'; + +const MAX_DATE = '9999-12-31 23:59:59'; +const MAX_DATE_LABEL = '9999-12-31'; + +/** + * 处理时间单位转换的兼容函数 + * @param value 时间值 + * @param unit 单位 + * @returns [转换后的值, 转换后的单位] + */ +const normalizeTimeUnit = (value: number, unit: TimeUnit): [number, TimeUnit] => { + if (unit === 'years' && value % 1 !== 0) { + // 处理年的小数情况,转换为月 + return [value * 12, 'months']; + } + return [value, unit]; +}; + +export const getExpireTime = (expireTime, customExpireTime, isCustomExpireTime) => { + if (isCustomExpireTime) { + return customExpireTime?.valueOf(); + } else { + const [offset, unit] = expireTime.split(',') ?? []; + if (offset === 'never') { + return dayjs(MAX_DATE)?.valueOf(); + } + const [normalizedValue, normalizedUnit] = normalizeTimeUnit(Number(offset), unit as TimeUnit); + return dayjs().add(normalizedValue, normalizedUnit)?.valueOf(); + } +}; + +export const getExpireTimeLabel = (expireTime) => { + const label = dayjs(expireTime).format('YYYY-MM-DD'); + return label === MAX_DATE_LABEL + ? formatMessage({ + id: 'src.component.Task.ApplyDatabasePermission.CreateModal.B5C7760D', + defaultMessage: '永不过期', + }) + : label; +}; diff --git a/src/d.ts/importTask.ts b/src/d.ts/importTask.ts index 1155ad241..abefc3fd8 100644 --- a/src/d.ts/importTask.ts +++ b/src/d.ts/importTask.ts @@ -1,5 +1,5 @@ import { formatMessage } from '@/util/intl'; -import { ConnectType, IConnection, TaskType } from '.'; +import { ConnectType, IConnection, TaskStatus, TaskType } from '.'; import { ODCCloudProvider } from './migrateTask'; export enum ScheduleNonImportableType { @@ -7,6 +7,8 @@ export enum ScheduleNonImportableType { TYPE_NOT_MATCH = 'TYPE_NOT_MATCH', DATASOURCE_NON_EXIST = 'DATASOURCE_NON_EXIST', IMPORTED = 'IMPORTED', + // 前端维护的待导入, 包含DATASOURCE_NON_EXIST, LACK_OF_INSTANCE和可导入的 + // TO_BE_IMPORTED = 'TO_BE_IMPORTED', } export const ScheduleNonImportableTypeMap = { @@ -22,10 +24,7 @@ export const ScheduleNonImportableTypeMap = { id: 'src.d.ts.B89ABE6D', defaultMessage: '类型不匹配', }), - [ScheduleNonImportableType.IMPORTED]: formatMessage({ - id: 'src.d.ts.7AE88D0A', - defaultMessage: '已导入', - }), + [ScheduleNonImportableType.IMPORTED]: '已存在', }; export interface IScheduleTaskImportRequest { @@ -35,7 +34,16 @@ export interface IScheduleTaskImportRequest { projectId: string; decryptKey: string; // 导入接口必须传 - importableExportRowId?: string[]; + scheduleTaskImportRows?: scheduleTaskImportRows[]; +} + +export interface scheduleTaskImportRows { + // 行ID + rowId: string; + // 手动指定的源数据库ID + databaseId: number; + // 手动指定的目标数据库ID + targetDatabaseId: number; } export interface IImportScheduleTaskView { @@ -60,8 +68,10 @@ export interface IImportScheduleTaskView { */ originProjectName: string; type: TaskType; - databaseView: IImportDatabaseView; - targetDatabaseView: IImportDatabaseView; + databaseView: IImportDatabaseView; // 源端 + targetDatabaseView: IImportDatabaseView; // 目标端 + description: string; + originStatus: TaskStatus; } export interface IImportDatabaseView { @@ -85,6 +95,7 @@ export interface IImportDatabaseView { */ matchedDatasourceName: string; databaseName: string; + matchedDatabaseId?: number; // 匹配到的源端 / 目标端数据库ID } export interface IImportTaskResult { @@ -94,6 +105,7 @@ export interface IImportTaskResult { exportRowId: string; success: boolean; failedReason: string; + remark?: string; } export enum IMPORT_TYPE { diff --git a/src/page/Project/User/ManageModal/Database/CreateAuth/index.tsx b/src/page/Project/User/ManageModal/Database/CreateAuth/index.tsx index 6e4c24d2a..762375698 100644 --- a/src/page/Project/User/ManageModal/Database/CreateAuth/index.tsx +++ b/src/page/Project/User/ManageModal/Database/CreateAuth/index.tsx @@ -18,9 +18,9 @@ import { formatMessage } from '@/util/intl'; import { addDatabasePermissions } from '@/common/network/project'; import { expireTimeOptions, - getExpireTime, permissionOptions, } from '@/component/Task/ApplyDatabasePermission/CreateModal'; +import { getExpireTime } from '@/component/Task/helper'; import DatabaseSelecter from '@/component/Task/component/DatabaseSelecter'; import { Button, Checkbox, DatePicker, Drawer, Form, message, Modal, Select, Space } from 'antd'; import React, { useState } from 'react'; diff --git a/src/page/Project/User/ManageModal/Database/TaskApplyList/index.tsx b/src/page/Project/User/ManageModal/Database/TaskApplyList/index.tsx index b1c5b9ac8..2ecf59287 100644 --- a/src/page/Project/User/ManageModal/Database/TaskApplyList/index.tsx +++ b/src/page/Project/User/ManageModal/Database/TaskApplyList/index.tsx @@ -24,7 +24,7 @@ import { ITableLoadOptions, } from '@/component/CommonTable/interface'; import SearchFilter from '@/component/SearchFilter'; -import { getExpireTimeLabel } from '@/component/Task/ApplyDatabasePermission'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import TaskDetailModal from '@/component/Task/DetailModal'; import type { IResponseData } from '@/d.ts'; import { TaskType } from '@/d.ts'; diff --git a/src/page/Project/User/ManageModal/Database/UserAuthList/index.tsx b/src/page/Project/User/ManageModal/Database/UserAuthList/index.tsx index c809ee0cd..dc5ac882c 100644 --- a/src/page/Project/User/ManageModal/Database/UserAuthList/index.tsx +++ b/src/page/Project/User/ManageModal/Database/UserAuthList/index.tsx @@ -23,7 +23,7 @@ import { ITableLoadOptions, } from '@/component/CommonTable/interface'; import SearchFilter from '@/component/SearchFilter'; -import { getExpireTimeLabel } from '@/component/Task/ApplyDatabasePermission'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import type { IResponseData } from '@/d.ts'; import { DatabasePermissionStatus, IDatabasePermission } from '@/d.ts/project'; import { SearchOutlined } from '@ant-design/icons'; diff --git a/src/page/Project/User/ManageModal/Table/CreateAuth/index.tsx b/src/page/Project/User/ManageModal/Table/CreateAuth/index.tsx index 64e7fe181..b0256721f 100644 --- a/src/page/Project/User/ManageModal/Table/CreateAuth/index.tsx +++ b/src/page/Project/User/ManageModal/Table/CreateAuth/index.tsx @@ -18,9 +18,10 @@ import { formatMessage } from '@/util/intl'; import { addTablePermissions } from '@/common/network/project'; import { expireTimeOptions, - getExpireTime, permissionOptions, } from '@/component/Task/ApplyTablePermission/CreateModal'; +import { getExpireTime } from '@/component/Task/helper'; + import TableSelecter from '@/component/Task/component/TableSelecter'; import { groupTableIdsByDataBase } from '@/component/Task/component/TableSelecter/util'; import { Button, Checkbox, DatePicker, Drawer, Form, Modal, Select, Space, message } from 'antd'; diff --git a/src/page/Project/User/ManageModal/Table/TaskApplyList/index.tsx b/src/page/Project/User/ManageModal/Table/TaskApplyList/index.tsx index 3864216c1..2444e3c3e 100644 --- a/src/page/Project/User/ManageModal/Table/TaskApplyList/index.tsx +++ b/src/page/Project/User/ManageModal/Table/TaskApplyList/index.tsx @@ -23,7 +23,7 @@ import { ITableLoadOptions, } from '@/component/CommonTable/interface'; import SearchFilter from '@/component/SearchFilter'; -import { getExpireTimeLabel } from '@/component/Task/ApplyDatabasePermission'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import TaskDetailModal from '@/component/Task/DetailModal'; import type { IResponseData } from '@/d.ts'; import { TaskType } from '@/d.ts'; diff --git a/src/page/Project/User/ManageModal/Table/UserAuthList/index.tsx b/src/page/Project/User/ManageModal/Table/UserAuthList/index.tsx index a14df8be8..d6e3c0493 100644 --- a/src/page/Project/User/ManageModal/Table/UserAuthList/index.tsx +++ b/src/page/Project/User/ManageModal/Table/UserAuthList/index.tsx @@ -23,7 +23,6 @@ import { ITableLoadOptions, } from '@/component/CommonTable/interface'; import SearchFilter from '@/component/SearchFilter'; -import { getExpireTimeLabel } from '@/component/Task/ApplyTablePermission'; import type { IResponseData } from '@/d.ts'; import { ITablePermission, TablePermissionStatus } from '@/d.ts/project'; import { SearchOutlined } from '@ant-design/icons'; @@ -35,6 +34,7 @@ import { tablePermissionTypeMap, } from '../'; import StatusLabel from '../Status'; +import { getExpireTimeLabel } from '@/component/Task/helper'; const getColumns = (params: { paramOptions: ITableLoadOptions; diff --git a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx index 7ffbef5ba..d8e0b3467 100644 --- a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx +++ b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx @@ -24,7 +24,7 @@ import login from '@/store/login'; import { formatMessage } from '@/util/intl'; import Icon, { ArrowDownOutlined, LoadingOutlined } from '@ant-design/icons'; import { useRequest } from 'ahooks'; -import { Divider, Select, Space } from 'antd'; +import { Divider, Flex, Select, Space } from 'antd'; import React, { useEffect, useState } from 'react'; import SessionContext from '../context'; import { DEFALT_WIDTH } from './const'; @@ -45,6 +45,9 @@ interface IProps { datasourceMode?: boolean; projectMode?: boolean; onChange?: (value: number, database?: IDatabase) => void; + showProject?: boolean; + popoverWidth?: number; + manageLinkVisible?: boolean; } const SelectItem: React.FC = ({ @@ -63,6 +66,9 @@ const SelectItem: React.FC = ({ isLogicalDatabase = false, datasourceMode = false, projectMode = isLogicalDatabase, + showProject = true, + popoverWidth, + manageLinkVisible = false, }) => { const { data: database, run: runDatabase } = useRequest(getDatabase, { manual: true, @@ -139,7 +145,10 @@ const SelectItem: React.FC = ({ } if (!datasourceMode && database?.data) { return ( - + <> = ({ style={{ fontSize: 16, marginRight: 4, verticalAlign: 'textBottom' }} /> - {database?.data?.name} - + +
+ {database?.data?.name} +
+ ); } return placeholder; @@ -175,9 +193,10 @@ const SelectItem: React.FC = ({ projectId={projectId} dataSourceId={dataSourceId} filters={filters} - width={width || DEFALT_WIDTH} + width={popoverWidth || width || DEFALT_WIDTH} taskType={taskType} disabled={disabled} + manageLinkVisible={manageLinkVisible} > + + )} + + {/* 动态字段:根据 schema 生成 */} + {provider?.modelCredentialSchema?.credentialFormSchemas + ?.filter((schema) => shouldShowField(schema)) + .map((schema) => ( + + {renderFormComponent(schema)} + + ))} + + + + + + + + ); +}); + +export default EditModal; diff --git a/src/page/ExternalIntegration/LargeModel/component/ModelSelect/index.less b/src/page/ExternalIntegration/LargeModel/component/ModelSelect/index.less new file mode 100644 index 000000000..5ffe6aba4 --- /dev/null +++ b/src/page/ExternalIntegration/LargeModel/component/ModelSelect/index.less @@ -0,0 +1,106 @@ +.llmDescription { + z-index: 2000; +} +.defaultModelContent { + display: inline-flex; + align-items: center; + .icon { + font-size: 16px; + margin-right: 4px; + align-items: center; + } +} +.modelDetailTitle { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: #5c6b8a; + line-height: 20px; +} +.modelDetailContent { + font-size: 12px; + line-height: 20px; +} +.wrapper { + width: 100%; + height: 94px; + margin-top: 8px; + background-color: #f8fafe; + padding-left: 20px; + padding-bottom: 26px; + display: flex; + align-items: flex-end; + .optionContainer { + display: flex; + } + .edit { + color: var(--icon-color-focus); + cursor: pointer; + margin-left: 4px; + } + .modelEditWrapper { + display: flex; + align-items: center; + .selectField { + display: inline-flex; + } + .editOperations { + display: inline-flex; + margin-left: 8px; + cursor: pointer; + gap: 8px; + } + } + + .formLayout { + display: inline-flex; + background-color: #f8fafe; + :global { + .ant-form-item-control-input-content { + display: inline-flex; + align-items: center; + } + } + } + .save { + display: inline-flex; + height: 28px; + margin-top: 42px; + } + .field { + margin-right: 24px; + min-width: 308px; + } + + .fieldWrapper { + display: flex; + flex-direction: row; + } + + .colonSeparator { + font-size: 12px; + } + + .selectInput { + width: 248px; + } + + .labelContainer { + white-space: nowrap; + font-size: 12px; + } + + .labelText { + color: var(--text-color-primary); + margin-right: 4px; + } + + .helpIcon { + display: inline-block; + width: 12px; + height: 12px; + color: var(--text-color-secondary); + font-size: 12px; + } +} diff --git a/src/page/ExternalIntegration/LargeModel/component/ModelSelect/index.tsx b/src/page/ExternalIntegration/LargeModel/component/ModelSelect/index.tsx new file mode 100644 index 000000000..7d9f31da5 --- /dev/null +++ b/src/page/ExternalIntegration/LargeModel/component/ModelSelect/index.tsx @@ -0,0 +1,425 @@ +import React, { useCallback, useState, useEffect, useMemo, useContext } from 'react'; +import { observer, inject } from 'mobx-react'; +import { + Button, + Col, + Form, + Popconfirm, + Popover, + Row, + Select, + Tag, + Tooltip, + message, + Typography, + Spin, +} from 'antd'; +import Icon, { + CheckOutlined, + CloseOutlined, + EditOutlined, + InfoCircleFilled, + QuestionCircleOutlined, +} from '@ant-design/icons'; + +import LargeModelSelectEmpty from '@/component/Empty/LargeModelSelectEmpty'; +import { modelSelectWarningTooltip, UI_SIZES, VendorsConfig } from '../../constant'; +import styles from './index.less'; +import { EModelSatus, type ModelSelectProps } from '@/d.ts/llm'; + +enum EFiledType { + LLM = 'llm', + CHAT = 'chat', + TEXT_EMBEDDING = 'textEmbedding', + BOTH = 'both', +} + +enum ESelectType { + LLM = 'LLM', + EMBEDDING = 'EMBEDDING', + CHAT = 'CHAT', + NONE = 'NONE', + ALL = 'ALL', +} + +const ModelSelect: React.FC = ({ + allModels, + modelsLoading, + aiConfig, + updateAIConfig, + updateLoading, + defaultModelStatuses, + getModelOptions, +}) => { + const [loading, setLoading] = useState(false); + const [currentEditingSelect, setCurrentEditingSelect] = useState(ESelectType.NONE); + const [llmValue, setLLMValue] = useState(null); + const [chatValue, setChatValue] = useState(null); + const [embeddingValue, setEmbeddingValue] = useState(null); + const [form] = Form.useForm(); + + const renderContent = (content: string) => { + if (!content) return false; + const [icon, label] = content?.split('/'); + return ( + + + {label} + + ); + }; + + // 渲染模型状态警告 + const renderModelStatusWarning = (status: EModelSatus) => { + if (status === EModelSatus.SUCCESS) { + return null; + } + + return ( + + + + ); + }; + + // 初始化表单值 + useEffect(() => { + const config = aiConfig; + if (config) { + // 根据配置初始化表单和状态 + const defaultChatModel = config.defaultChatModel; + const defaultLlmModel = config.defaultLlmModel; + const defaultEmbeddingModel = config.defaultEmbeddingModel; + + form.setFieldsValue({ + llm: defaultLlmModel, + chat: defaultChatModel, + textEmbedding: defaultEmbeddingModel, + }); + + if (defaultEmbeddingModel) { + setCurrentEditingSelect(ESelectType.NONE); + } else { + setCurrentEditingSelect(ESelectType.ALL); + } + + // 更新本地状态 + setLLMValue(defaultLlmModel); + setChatValue(defaultChatModel); + setEmbeddingValue(defaultEmbeddingModel); + } + }, [aiConfig, form]); + + const isEditing = (type) => { + return currentEditingSelect === type || currentEditingSelect === ESelectType.ALL; + }; + + // 生成 LLM 模型选项 + const llmOptions = getModelOptions('CHAT'); + + // 生成 Embedding 模型选项 + const embedingOptions = getModelOptions('EMBEDDING'); + + const handleSaveField = useCallback( + async (fieldType: EFiledType) => { + const ll = aiConfig; + + if (!aiConfig) return; + + if (fieldType === EFiledType.BOTH) { + await form.validateFields(); + } + try { + setLoading(true); + const values = form.getFieldsValue(); + const currentConfig = aiConfig; + + // 构建更新数据 - 只传递变更的字段 + let updateData: any = {}; + + switch (fieldType) { + case EFiledType.LLM: + updateData.defaultLlmModel = values.llm; + setLLMValue(values.llm); + + break; + case EFiledType.CHAT: + updateData.defaultChatModel = values.chat; + setChatValue(values.chat); + break; + case EFiledType.TEXT_EMBEDDING: + updateData.defaultEmbeddingModel = values.textEmbedding; + setEmbeddingValue(values.textEmbedding); + break; + case EFiledType.BOTH: + await form.validateFields(); + updateData.defaultLlmModel = values.llm; + updateData.defaultChatModel = values.chat; + updateData.defaultEmbeddingModel = values.textEmbedding; + setLLMValue(values.llm); + setChatValue(values.chat); + setEmbeddingValue(values.textEmbedding); + break; + } + // 调用更新API + await updateAIConfig({ + ...aiConfig, + ...updateData, + }); + } catch (e) { + // message.error('提交失败'); + console.error('保存失败:', e); + } finally { + setCurrentEditingSelect(ESelectType.NONE); + setLoading(false); + } + }, + [form, aiConfig], + ); + + const renderOption = (option) => { + const [vendorType, value] = (option.value as string)?.split('/'); + const config = VendorsConfig[vendorType]; + return ( + + + {config?.label} + + } + content={ +
+
{option?.label || '-'}
+
+ {option?.data?.data?.model?.modelType || '-'} +
+
+ } + > + + + {value} + +
+ ); + }; + const renderLabel = ({ label, tooltip }: { label: string; tooltip: string }) => { + return ( + + {label} + + + + + ); + }; + + const spinningStatus = loading || updateLoading || modelsLoading; + + return ( + +
+
+ +
+ + {isEditing(ESelectType.LLM) ? ( +
+ + } + optionRender={(option) => renderOption(option)} + options={llmOptions} + styles={{ popup: { root: { maxHeight: 360, overflowY: 'scroll' } } }} + /> + + {chatValue && ( +
+ handleSaveField(EFiledType.CHAT)} + /> + { + setCurrentEditingSelect(ESelectType.NONE); + }} + /> +
+ )} +
+ ) : ( + <> + {renderContent(chatValue) || ( + (暂未选择模型) + )} + {renderModelStatusWarning( + defaultModelStatuses?.chatStatus || EModelSatus.SUCCESS, + )} + { + setCurrentEditingSelect(ESelectType.CHAT); + }} + /> + + )} +
+ + + + {isEditing(ESelectType.EMBEDDING) ? ( +
+ + , + }, + { + label: '访问地址', + name: 'visitUrl', + rules: [{ required: true, message: '请输入访问地址' }], + component: , + }, + { + label: 'API Key', + name: 'apiKey', + component: , + }, + { + label: '上下文长度', + name: 'contextLength', + rules: [{ required: true, message: '请输入上下文长度' }], + component: , + }, + ], + [EVendorType.TONGYI]: [ + { + label: '模型类型', + name: 'modelType', + initialValue: 'LLM', + rules: [{ required: true, message: '请输入类型' }], + component: , + }, + { + label: '模型名称', + name: 'modelName', + rules: [{ required: true, message: '请输入名称' }], + component: , + }, + { + label: '访问地址', + name: 'visitUrl', + rules: [{ required: true, message: '请输入访问地址' }], + component: , + }, + { + label: 'API Key', + name: 'apiKey', + component: , + }, + { + label: '上下文长度', + name: 'contextLength', + rules: [{ required: true, message: '请输入上下文长度' }], + component: , + }, + ], + [EVendorType.OPEN_AI]: [ + { + label: '模型类型', + name: 'modelType', + initialValue: 'LLM', + rules: [{ required: true, message: '请输入类型' }], + component: , + }, + { + label: '模型名称', + name: 'modelName', + rules: [{ required: true, message: '请输入名称' }], + component: , + }, + { + label: 'API Key', + name: 'apiKey', + component: , + }, + { + label: 'API endpoint URL', + name: 'endpointUrl', + rules: [{ required: true, message: '请输入 API endpoint URL' }], + placeholder: , + }, + { + label: 'API endpoint中的模型名称', + component: , + }, + { + label: '上下文长度', + name: 'contextLength', + rules: [{ required: true, message: '请输入上下文长度' }], + component: , + }, + ], + [EVendorType.DOUBAO]: [ + { + label: '模型类型', + name: 'modelType', + initialValue: 'LLM', + rules: [{ required: true, message: '请输入类型' }], + component: , + }, + { + label: '模型名称', + name: 'modelName', + rules: [{ required: true, message: '请输入名称' }], + component: , + }, + { label: '鉴权方式', name: 'auth', component: }, + { + label: 'API Key', + name: 'apiKey', + component: , + }, + { label: '火山引擎地域', name: 'engine', component: }, + { + label: 'API Endpoint Host', + name: 'host', + component: , + }, + { + label: 'Endpoint ID', + name: 'endpointId', + component: , + }, + { label: '基础模型', name: 'model', component: ; + case ESchemaFieldType.SELECT: + return ( + + ); + default: + return ; + } +}; + +/** + * 阻止事件冒泡的通用处理器 + */ +export const stopPropagation = (callback: () => void) => (e: React.MouseEvent) => { + e.stopPropagation(); + callback(); +}; + +/** + * 设置当前Provider并执行回调 + */ +export const withProviderAction = ( + provider: IModelProvider | undefined, + largeModelStore, + action: (store) => void, +) => { + if (provider && largeModelStore) { + largeModelStore.setCurrentProvider(provider); + action(largeModelStore); + } +}; + +/** + * 获取API Key按钮文案 + */ +export const getApiKeyButtonText = (hasApiKey: boolean): string => { + return hasApiKey ? TEXT_CONSTANTS.MODIFY_API_KEY : TEXT_CONSTANTS.CONFIGURE_API_KEY; +}; + +/** + * 获取状态点颜色 + */ +export const getStatusDotColor = (hasApiKey: boolean): string => { + return hasApiKey ? UI_COLORS.SUCCESS_GREEN : UI_COLORS.ERROR_RED; +}; + +/** + * 检查是否支持某个配置方法 + */ +export const supportsConfigurationMethod = ( + provider: IModelProvider | undefined, + method: EConfigurationMethod, +): boolean => { + return provider?.configurateMethods?.includes(method) || false; +}; + +/** + * 格式化模型数量显示 + */ +export const formatModelCount = (count: number): string => { + return `${count} 个模型`; +}; + +/** + * 创建状态点样式对象 + */ +export const createStatusDotStyle = (hasApiKey: boolean): React.CSSProperties => ({ + backgroundColor: getStatusDotColor(hasApiKey), +}); diff --git a/src/page/ExternalIntegration/index.tsx b/src/page/ExternalIntegration/index.tsx index be117bbaa..8029a15ec 100644 --- a/src/page/ExternalIntegration/index.tsx +++ b/src/page/ExternalIntegration/index.tsx @@ -21,10 +21,14 @@ import { history, useParams } from '@umijs/max'; import React from 'react'; import SqlInterceptor from './SqlInterceptor'; import SSO from './SSO'; +import LargeModel from './LargeModel'; interface IProps {} const Pages = { + [IPageType.Large_Model]: { + component: LargeModel, + }, [IPageType.ExternalIntegration_Approval]: { component: SqlInterceptor, }, @@ -37,6 +41,10 @@ const Pages = { }; const tabs = [ + { + tab: '大模型集成', + key: IPageType.Large_Model, + }, { tab: formatMessage({ id: 'odc.page.ExternalIntegration.ApprovalIntegration', @@ -79,7 +87,11 @@ const Index: React.FC = function () { tabActiveKey={page} onTabChange={handleChange} > - + {page === IPageType.Large_Model ? ( + + ) : ( + + )} ); }; diff --git a/src/svgr/noData.svg b/src/svgr/noData.svg new file mode 100644 index 000000000..e54dc88a3 --- /dev/null +++ b/src/svgr/noData.svg @@ -0,0 +1,22 @@ + + + Z#/2.缺省图/暂无数据-组件 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/noModels.svg b/src/svgr/noModels.svg new file mode 100644 index 000000000..851f5eae6 --- /dev/null +++ b/src/svgr/noModels.svg @@ -0,0 +1,28 @@ + + + 缺省图-设置模型 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/deepseek.svg b/src/svgr/vendor/deepseek.svg new file mode 100644 index 000000000..0471f40fc --- /dev/null +++ b/src/svgr/vendor/deepseek.svg @@ -0,0 +1,12 @@ + + + DeepSeek-正常 + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/doubao.svg b/src/svgr/vendor/doubao.svg new file mode 100644 index 000000000..04fdaa8f1 --- /dev/null +++ b/src/svgr/vendor/doubao.svg @@ -0,0 +1,11 @@ + + + 豆包 -正常 + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/grey-deepseek.svg b/src/svgr/vendor/grey-deepseek.svg new file mode 100644 index 000000000..7ca9a3b5b --- /dev/null +++ b/src/svgr/vendor/grey-deepseek.svg @@ -0,0 +1,12 @@ + + + DeepSeek-置灰 + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/grey-doubao.svg b/src/svgr/vendor/grey-doubao.svg new file mode 100644 index 000000000..c58a2964a --- /dev/null +++ b/src/svgr/vendor/grey-doubao.svg @@ -0,0 +1,11 @@ + + + 豆包-置灰 + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/grey-ollama.svg b/src/svgr/vendor/grey-ollama.svg new file mode 100644 index 000000000..f5856dbd6 --- /dev/null +++ b/src/svgr/vendor/grey-ollama.svg @@ -0,0 +1,18 @@ + + + Ollama-置灰 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/grey-openAI.svg b/src/svgr/vendor/grey-openAI.svg new file mode 100644 index 000000000..fd925fe5c --- /dev/null +++ b/src/svgr/vendor/grey-openAI.svg @@ -0,0 +1,11 @@ + + + OpenAl-API-compatible-置灰 + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/grey-tongyi.svg b/src/svgr/vendor/grey-tongyi.svg new file mode 100644 index 000000000..fd86f1603 --- /dev/null +++ b/src/svgr/vendor/grey-tongyi.svg @@ -0,0 +1,12 @@ + + + 通义千问-置灰 + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/grey-vllm.svg b/src/svgr/vendor/grey-vllm.svg new file mode 100644 index 000000000..71e28ac9d --- /dev/null +++ b/src/svgr/vendor/grey-vllm.svg @@ -0,0 +1,18 @@ + + + Vllm-置灰 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/ollama.svg b/src/svgr/vendor/ollama.svg new file mode 100644 index 000000000..5c3ba3dfa --- /dev/null +++ b/src/svgr/vendor/ollama.svg @@ -0,0 +1,18 @@ + + + Ollama-正常 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/openAI.svg b/src/svgr/vendor/openAI.svg new file mode 100644 index 000000000..b38f1cf95 --- /dev/null +++ b/src/svgr/vendor/openAI.svg @@ -0,0 +1,11 @@ + + + OpenAl-API-compatible + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/tongyi.svg b/src/svgr/vendor/tongyi.svg new file mode 100644 index 000000000..80ca1c589 --- /dev/null +++ b/src/svgr/vendor/tongyi.svg @@ -0,0 +1,12 @@ + + + 通义千问-正常备份 + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/vendor/vllm.svg b/src/svgr/vendor/vllm.svg new file mode 100644 index 000000000..8f3d8c4ca --- /dev/null +++ b/src/svgr/vendor/vllm.svg @@ -0,0 +1,18 @@ + + + Vllm-正常 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/util/intl.tsx b/src/util/intl.tsx index a8113a8fd..18a98070f 100644 --- a/src/util/intl.tsx +++ b/src/util/intl.tsx @@ -73,6 +73,15 @@ export function getLocalDocs(hash?: string) { return window.publicPath + 'help-doc/' + local + '/index.html' + (hash ? `#/${hash}` : ''); } +export function getServerLocalKey() { + let local: string = getEnvLocale(); + local = local.toLowerCase(); + if (local === 'zh-cn') { + return 'zh_Hans'; + } + return 'en_US'; +} + export function getOBDocsUrl(key?: string) { let local: string = getEnvLocale(); local = local.toLowerCase(); diff --git a/src/util/request/largeModel.ts b/src/util/request/largeModel.ts new file mode 100644 index 000000000..7dc98fec9 --- /dev/null +++ b/src/util/request/largeModel.ts @@ -0,0 +1,173 @@ +/* + * 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, + IModelInfo, + 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 deleteModelProvider(provider: string): Promise { + const result = await request.delete('/api/v2/integration/llm/providers', { + params: { + provider, + }, + }); + 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/util/utils.ts b/src/util/utils.ts index 440baff39..9b1df54f3 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -758,3 +758,15 @@ export const uniqueTools = (tools) => { export const flatArray = (array: any[]): any[] => { return array?.reduce?.((pre, cur) => pre?.concat(Array.isArray(cur) ? flatArray(cur) : cur), []); }; + +export const maskAPIKey = (apiKey: string) => { + if (apiKey.length <= 3) { + return apiKey; // 如果长度小于等于3,直接返回原字符串 + } + + const firstPart = apiKey.slice(0, 2); // 取前两位 + const lastPart = apiKey.slice(-1); // 取最后一位 + const maskedPart = '*'.repeat(apiKey.length - 3); // 生成遮盖部分 + + return `${firstPart}${maskedPart}${lastPart}`; // 拼接结果 +}; From 994ab2ff4aa49c345dfd4ab942b86b5c15720392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Fri, 1 Aug 2025 09:30:10 +0800 Subject: [PATCH 004/239] =?UTF-8?q?PullRequest:=20913=20=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8F=B0-=E6=9C=80=E8=BF=91=E6=95=B0=E6=8D=AE=E5=BA=93-?= =?UTF-8?q?=E7=A6=81=E7=94=A8OSS=E7=AD=89=E7=89=B9=E5=AE=9A=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/console-oss of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/913 Reviewed-by: 晓康 * feat: 工作台-最近访问数据库-禁用特定数据源类型的数据库 * refactor: 优化名称 * refactor: 使用枚举定义禁用的数据源类型 * refactor: 名称优化 * refactor: 优化命名、点击事件 * feat: 添加tooltip 枚举 * refactor: 优化命名 * feat: tooltip 用法更新到antd5用法 * refactor: 合并多余代码 * refactor: 优化枚举名称 * feat: 升级ts版本 --- package.json | 6 +- pnpm-lock.yaml | 197 +++++++++--------- src/component/Log/index.tsx | 2 +- src/d.ts/datasource.ts | 7 + .../components/RecentlyDatabase/index.less | 5 +- .../components/RecentlyDatabase/index.tsx | 189 ++++++++++------- 6 files changed, 227 insertions(+), 179 deletions(-) diff --git a/package.json b/package.json index 0719029cb..3dce90d7b 100644 --- a/package.json +++ b/package.json @@ -71,11 +71,10 @@ "tree-kill": "^1.2.1" }, "devDependencies": { - "axios": "^1.7.7", + "@ant-design/icons": "^4.0.0", "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", - "@ant-design/icons": "^4.0.0", "@oceanbase-odc/monaco-plugin-ob": "~1.4.2", "@oceanbase-odc/ob-intl-cli": "^2.1.3", "@oceanbase-odc/ob-parser-js": "^3.0.5", @@ -102,6 +101,7 @@ "antlr4": "~4.8.0", "array-move": "^4.0.0", "aws-sdk": "^2.1231.0", + "axios": "^1.7.7", "bignumber.js": "^9.0.0", "blueimp-md5": "^2.19.0", "cherio": "^1.0.0-rc.2", @@ -162,7 +162,7 @@ "tar": "^6.1.11", "ts-is-present": "^1.1.3", "ts-loader": "8.4.0", - "typescript": "^4.0.0", + "typescript": "^5.8.3", "webpack": "^4.28.0", "webpack-cli": "^3.2.1", "webpack-merge": "^4.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68c485d6f..8dbbf93b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,7 +56,7 @@ importers: version: 1.4.2(monaco-editor@0.36.1) '@oceanbase-odc/ob-intl-cli': specifier: ^2.1.3 - version: 2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@4.9.5) + version: 2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.8.3) '@oceanbase-odc/ob-parser-js': specifier: ^3.0.5 version: 3.0.5 @@ -107,7 +107,7 @@ importers: version: 3.0.0 '@umijs/max': specifier: ^4.0.66 - version: 4.4.11(@babel/core@7.27.4)(@types/node@9.6.61)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + version: 4.4.11(@babel/core@7.27.4)(@types/node@9.6.61)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) adm-zip: specifier: ^0.5.5 version: 0.5.16 @@ -278,7 +278,7 @@ importers: version: 17.0.2(react@17.0.2) react-intl: specifier: ^5.20.10 - version: 5.25.1(react@17.0.2)(typescript@4.9.5) + version: 5.25.1(react@17.0.2)(typescript@5.8.3) react-resizable: specifier: ^1.10.1 version: 1.11.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -311,10 +311,10 @@ importers: version: 1.2.2 ts-loader: specifier: 8.4.0 - version: 8.4.0(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + version: 8.4.0(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) typescript: - specifier: ^4.0.0 - version: 4.9.5 + specifier: ^5.8.3 + version: 5.8.3 webpack: specifier: ^4.28.0 version: 4.47.0(webpack-cli@3.3.12) @@ -1894,49 +1894,42 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.0.1': resolution: {integrity: sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.0.1': resolution: {integrity: sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.0.1': resolution: {integrity: sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.0.1': resolution: {integrity: sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.0.1': resolution: {integrity: sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.0.1': resolution: {integrity: sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@napi-rs/nice-win32-arm64-msvc@1.0.1': resolution: {integrity: sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==} @@ -2328,28 +2321,24 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [glibc] '@swc/core-linux-arm64-musl@1.11.29': resolution: {integrity: sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [musl] '@swc/core-linux-x64-gnu@1.11.29': resolution: {integrity: sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [glibc] '@swc/core-linux-x64-musl@1.11.29': resolution: {integrity: sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [musl] '@swc/core-win32-arm64-msvc@1.11.29': resolution: {integrity: sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==} @@ -2905,28 +2894,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@umijs/es-module-parser-linux-arm64-musl@0.0.7': resolution: {integrity: sha512-cqQffARWkmQ3n1RYNKZR3aD6X8YaP6u1maASjDgPQOpZMAlv/OSDrM/7iGujWTs0PD0haockNG9/DcP6lgPHMw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@umijs/es-module-parser-linux-x64-gnu@0.0.7': resolution: {integrity: sha512-PHrKHtT665Za0Ydjch4ACrNpRU+WIIden12YyF1CtMdhuLDSoU6UfdhF3NoDbgEUcXVDX/ftOqmj0SbH3R1uew==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@umijs/es-module-parser-linux-x64-musl@0.0.7': resolution: {integrity: sha512-cyZvUK5lcECLWzLp/eU1lFlCETcz+LEb+wrdARQSST1dgoIGZsT4cqM1WzYmdZNk3o883tiZizLt58SieEiHBQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@umijs/es-module-parser-win32-arm64-msvc@0.0.7': resolution: {integrity: sha512-V7WxnUI88RboSl0RWLNQeKBT7EDW35fW6Tn92zqtoHHxrhAIL9DtDyvC8REP4qTxeZ6Oej/Ax5I6IjsLx3yTOg==} @@ -2971,28 +2956,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@umijs/mako-linux-arm64-musl@0.11.10': resolution: {integrity: sha512-kqI1Jw6IHtDwrcsqPZrYxsV3pHzZyOR+6fCFnF5MSURnXbUbJb6Rk66VsKKpMqbyfsEO6nt0WT9FrRBlFvRU2A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@umijs/mako-linux-x64-gnu@0.11.10': resolution: {integrity: sha512-jlhXVvWJuumMmiE3z3ViugOMx9ZasNM1anng0PsusCgDwfy0IOfGzfwfwagqtzfsC5MwyRcfnRQyDdbfbroaSA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@umijs/mako-linux-x64-musl@0.11.10': resolution: {integrity: sha512-SLV/PRdL12dFEKlQGenW3OboZXmdYi25y+JblgVJLBhpdxZrHFqpCsTZn4L3hVEhyl0/ksR1iY0wtfK3urR29g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@umijs/mako-win32-ia32-msvc@0.11.10': resolution: {integrity: sha512-quCWpVl7yQjG+ccGhkF81GxO3orXdPW1OZWXWxJgOI0uPk7Hczh2EYMEVqqQGbi/83eJ1e3iE1jRTl/+2eHryQ==} @@ -5379,7 +5360,7 @@ packages: resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==} engines: {node: '>= 4.0'} os: [darwin] - deprecated: Upgrade to fsevents v2 to mitigate potential security issues + deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2 fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -6497,28 +6478,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.22.1: resolution: {integrity: sha512-MCV6RuRpzXbunvzwY644iz8cw4oQxvW7oer9xPkdadYqlEyiJJ6wl7FyJOH7Q6ZYH4yjGAUCvxDBxPbnDu9ZVg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.22.1: resolution: {integrity: sha512-RjNgpdM20VUXgV7us/VmlO3Vn2ZRiDnc3/bUxCVvySZWPiVPprpqW/QDWuzkGa+NCUf6saAM5CLsZLSxncXJwg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.22.1: resolution: {integrity: sha512-ZgO4C7Rd6Hv/5MnyY2KxOYmIlzk4rplVolDt3NbkNR8DndnyX0Q5IR4acJWNTBICQ21j3zySzKbcJaiJpk/4YA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-x64-msvc@1.22.1: resolution: {integrity: sha512-4pozV4eyD0MDET41ZLHAeBo+H04Nm2UEYIk5w/ts40231dRFV7E0cjwbnZvSoc1DXFgecAhiC0L16ruv/ZDCpg==} @@ -12057,7 +12034,7 @@ snapshots: '@formatjs/intl-utils@2.3.0': {} - '@formatjs/intl@2.2.1(typescript@4.9.5)': + '@formatjs/intl@2.2.1(typescript@5.8.3)': dependencies: '@formatjs/ecma402-abstract': 1.11.4 '@formatjs/fast-memoize': 1.2.1 @@ -12067,7 +12044,7 @@ snapshots: intl-messageformat: 9.13.0 tslib: 2.8.1 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 '@gar/promisify@1.1.3': {} @@ -12373,7 +12350,7 @@ snapshots: comlink: 4.4.2 monaco-editor: 0.36.1 - '@oceanbase-odc/ob-intl-cli@2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@4.9.5)': + '@oceanbase-odc/ob-intl-cli@2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.8.3)': dependencies: '@babel/core': 7.27.4 '@babel/generator': 7.27.3 @@ -12399,8 +12376,8 @@ snapshots: google-translate-api-x: 10.7.2 lodash: 4.17.21 node-fetch: 2.6.7(encoding@0.1.13) - prettier-eslint: 16.4.2(typescript@4.9.5) - prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@4.9.5) + prettier-eslint: 16.4.2(typescript@5.8.3) + prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@5.8.3) prettier-plugin-packagejson: 2.5.15(prettier@2.8.8) transitivePeerDependencies: - '@swc/helpers' @@ -13132,7 +13109,7 @@ snapshots: '@types/history@5.0.0': dependencies: - history: 4.10.1 + history: 5.3.0 '@types/hoist-non-react-statics@3.3.6': dependencies: @@ -13219,7 +13196,7 @@ snapshots: '@types/history': 4.7.11 '@types/react': 16.14.65 '@types/react-router': 5.1.20 - redux: 3.7.2 + redux: 4.2.1 '@types/react-router@5.1.20': dependencies: @@ -13307,22 +13284,22 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5)': + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@5.8.3) '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.35.0)(typescript@4.9.5) - '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/type-utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) debug: 4.4.1 eslint: 8.35.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 semver: 7.7.2 - tsutils: 3.21.0(typescript@4.9.5) + tsutils: 3.21.0(typescript@5.8.3) optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -13351,28 +13328,28 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5)': + '@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) debug: 4.4.1 eslint: 8.35.0 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@4.9.5)': + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.1 eslint: 8.57.1 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -13403,15 +13380,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@5.62.0(eslint@8.35.0)(typescript@4.9.5)': + '@typescript-eslint/type-utils@5.62.0(eslint@8.35.0)(typescript@5.8.3)': dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) - '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) debug: 4.4.1 eslint: 8.35.0 - tsutils: 3.21.0(typescript@4.9.5) + tsutils: 3.21.0(typescript@5.8.3) optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -13449,7 +13426,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@6.21.0(typescript@4.9.5)': + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.1 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.7.2 + tsutils: 3.21.0(typescript@4.9.5) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 @@ -13458,9 +13449,9 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.7.2 - ts-api-utils: 1.4.3(typescript@4.9.5) + ts-api-utils: 1.4.3(typescript@5.8.3) optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -13479,14 +13470,14 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@5.62.0(eslint@8.35.0)(typescript@4.9.5)': + '@typescript-eslint/utils@5.62.0(eslint@8.35.0)(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.35.0) '@types/json-schema': 7.0.15 '@types/semver': 7.7.0 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) eslint: 8.35.0 eslint-scope: 5.1.1 semver: 7.7.2 @@ -13536,10 +13527,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@umijs/bundler-mako@0.11.10(postcss@8.5.4)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12))': + '@umijs/bundler-mako@0.11.10(postcss@8.5.4)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12))': dependencies: '@umijs/bundler-utils': 4.4.11 - '@umijs/mako': 0.11.10(postcss@8.5.4)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + '@umijs/mako': 0.11.10(postcss@8.5.4)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) chalk: 4.1.2 compression: 1.8.0 connect-history-api-fallback: 2.0.0 @@ -13593,7 +13584,7 @@ snapshots: - supports-color - terser - '@umijs/bundler-webpack@4.4.11(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12))': + '@umijs/bundler-webpack@4.4.11(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12))': dependencies: '@svgr/core': 6.5.1 '@svgr/plugin-jsx': 6.5.1(@svgr/core@6.5.1) @@ -13608,7 +13599,7 @@ snapshots: cors: 2.8.5 css-loader: 6.7.1(webpack@4.47.0(webpack-cli@3.3.12)) es5-imcompatible-versions: 0.1.90 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) jest-worker: 29.4.3 lightningcss: 1.22.1 node-libs-browser: 2.2.1 @@ -13722,15 +13713,15 @@ snapshots: '@babel/runtime': 7.23.6 query-string: 6.14.1 - '@umijs/lint@4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.9.5)': + '@umijs/lint@4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.8.3)': dependencies: '@babel/core': 7.23.6 '@babel/eslint-parser': 7.23.3(@babel/core@7.23.6)(eslint@8.35.0) '@stylelint/postcss-css-in-js': 0.38.0(postcss-syntax@0.36.2(postcss@8.5.4))(postcss@8.5.4) - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5) - '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@5.8.3) '@umijs/babel-preset-umi': 4.4.11 - eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5) + eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3) eslint-plugin-react: 7.33.2(eslint@8.35.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.35.0) postcss: 8.5.4 @@ -13772,7 +13763,7 @@ snapshots: '@umijs/mako-win32-x64-msvc@0.11.10': optional: true - '@umijs/mako@0.11.10(postcss@8.5.4)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12))': + '@umijs/mako@0.11.10(postcss@8.5.4)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12))': dependencies: '@module-federation/webpack-bundler-runtime': 0.8.12 '@swc/helpers': 0.5.1 @@ -13786,7 +13777,7 @@ snapshots: lodash: 4.17.21 node-libs-browser-okam: 2.2.5 piscina: 4.9.2 - postcss-loader: 8.1.1(postcss@8.5.4)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + postcss-loader: 8.1.1(postcss@8.5.4)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) react-error-overlay: 6.0.9 react-refresh: 0.14.2 resolve: 1.22.10 @@ -13811,14 +13802,14 @@ snapshots: - typescript - webpack - '@umijs/max@4.4.11(@babel/core@7.27.4)(@types/node@9.6.61)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12))': + '@umijs/max@4.4.11(@babel/core@7.27.4)(@types/node@9.6.61)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12))': dependencies: - '@umijs/lint': 4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.9.5) + '@umijs/lint': 4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.8.3) '@umijs/plugins': 4.4.11(@babel/core@7.27.4)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(antd@4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(rc-field-form@2.7.0(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) antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) eslint: 8.35.0 stylelint: 14.8.2 - umi: 4.4.11(@babel/core@7.27.4)(@types/node@9.6.61)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + umi: 4.4.11(@babel/core@7.27.4)(@types/node@9.6.61)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) transitivePeerDependencies: - '@babel/core' - '@rspack/core' @@ -13917,17 +13908,17 @@ snapshots: - react-native - supports-color - '@umijs/preset-umi@4.4.11(@types/node@9.6.61)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12))': + '@umijs/preset-umi@4.4.11(@types/node@9.6.61)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12))': dependencies: '@iconify/utils': 2.1.1 '@svgr/core': 6.5.1 '@umijs/ast': 4.4.11 '@umijs/babel-preset-umi': 4.4.11 '@umijs/bundler-esbuild': 4.4.11 - '@umijs/bundler-mako': 0.11.10(postcss@8.5.4)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + '@umijs/bundler-mako': 0.11.10(postcss@8.5.4)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) '@umijs/bundler-utils': 4.4.11 '@umijs/bundler-vite': 4.4.11(@types/node@9.6.61)(lightningcss@1.22.1)(postcss@8.5.4)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.40.0) - '@umijs/bundler-webpack': 4.4.11(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + '@umijs/bundler-webpack': 4.4.11(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) '@umijs/core': 4.4.11 '@umijs/did-you-know': 1.0.3 '@umijs/es-module-parser': 0.0.7 @@ -15544,14 +15535,14 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@9.0.0(typescript@4.9.5): + cosmiconfig@9.0.0(typescript@5.8.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 crc-32@1.2.2: {} @@ -16511,12 +16502,12 @@ snapshots: - supports-color - typescript - eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5): + eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3): dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) eslint: 8.35.0 optionalDependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3) transitivePeerDependencies: - supports-color - typescript @@ -17166,7 +17157,7 @@ snapshots: forever-agent@0.6.1: {} - fork-ts-checker-webpack-plugin@8.0.0(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -17180,7 +17171,7 @@ snapshots: schema-utils: 3.3.0 semver: 7.7.2 tapable: 2.2.2 - typescript: 4.9.5 + typescript: 5.8.3 webpack: 4.47.0(webpack-cli@3.3.12) form-data@2.3.3: @@ -19652,9 +19643,9 @@ snapshots: dependencies: postcss: 8.5.4 - postcss-loader@8.1.1(postcss@8.5.4)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)): + postcss-loader@8.1.1(postcss@8.5.4)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)): dependencies: - cosmiconfig: 9.0.0(typescript@4.9.5) + cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.7 postcss: 8.5.4 semver: 7.7.2 @@ -19846,9 +19837,9 @@ snapshots: prelude-ls@1.2.1: {} - prettier-eslint@16.4.2(typescript@4.9.5): + prettier-eslint@16.4.2(typescript@5.8.3): dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.3) common-tags: 1.8.2 dlv: 1.1.3 eslint: 8.57.1 @@ -19869,6 +19860,11 @@ snapshots: prettier: 2.8.8 typescript: 4.9.5 + prettier-plugin-organize-imports@3.2.4(prettier@2.8.8)(typescript@5.8.3): + dependencies: + prettier: 2.8.8 + typescript: 5.8.3 + prettier-plugin-packagejson@2.4.3(prettier@2.8.8): dependencies: sort-package-json: 2.4.1 @@ -20795,11 +20791,11 @@ snapshots: react: 17.0.2 shallow-equal: 1.2.1 - react-intl@5.25.1(react@17.0.2)(typescript@4.9.5): + react-intl@5.25.1(react@17.0.2)(typescript@5.8.3): dependencies: '@formatjs/ecma402-abstract': 1.11.4 '@formatjs/icu-messageformat-parser': 2.1.0 - '@formatjs/intl': 2.2.1(typescript@4.9.5) + '@formatjs/intl': 2.2.1(typescript@5.8.3) '@formatjs/intl-displaynames': 5.4.3 '@formatjs/intl-listformat': 6.5.3 '@types/hoist-non-react-statics': 3.3.6 @@ -20809,7 +20805,7 @@ snapshots: react: 17.0.2 tslib: 2.8.1 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 react-is@16.13.1: {} @@ -22319,20 +22315,20 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 - ts-api-utils@1.4.3(typescript@4.9.5): + ts-api-utils@1.4.3(typescript@5.8.3): dependencies: - typescript: 4.9.5 + typescript: 5.8.3 ts-is-present@1.2.2: {} - ts-loader@8.4.0(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)): + ts-loader@8.4.0(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)): dependencies: chalk: 4.1.2 enhanced-resolve: 4.5.0 loader-utils: 2.0.4 micromatch: 4.0.8 semver: 7.7.2 - typescript: 4.9.5 + typescript: 5.8.3 webpack: 4.47.0(webpack-cli@3.3.12) tslib@1.14.1: {} @@ -22346,6 +22342,11 @@ snapshots: tslib: 1.14.1 typescript: 4.9.5 + tsutils@3.21.0(typescript@5.8.3): + dependencies: + tslib: 1.14.1 + typescript: 5.8.3 + tsx@3.12.2: dependencies: '@esbuild-kit/cjs-loader': 2.4.4 @@ -22431,19 +22432,19 @@ snapshots: uc.micro@1.0.6: {} - umi@4.4.11(@babel/core@7.27.4)(@types/node@9.6.61)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)): + umi@4.4.11(@babel/core@7.27.4)(@types/node@9.6.61)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)): dependencies: '@babel/runtime': 7.23.6 '@umijs/bundler-utils': 4.4.11 - '@umijs/bundler-webpack': 4.4.11(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + '@umijs/bundler-webpack': 4.4.11(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) '@umijs/core': 4.4.11 - '@umijs/lint': 4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.9.5) - '@umijs/preset-umi': 4.4.11(@types/node@9.6.61)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0(webpack-cli@3.3.12)) + '@umijs/lint': 4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.8.3) + '@umijs/preset-umi': 4.4.11(@types/node@9.6.61)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.40.0)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0(webpack-cli@3.3.12)) '@umijs/renderer-react': 4.4.11(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@umijs/server': 4.4.11 '@umijs/test': 4.4.11(@babel/core@7.27.4) '@umijs/utils': 4.4.11 - prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@4.9.5) + prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@5.8.3) prettier-plugin-packagejson: 2.4.3(prettier@2.8.8) transitivePeerDependencies: - '@babel/core' diff --git a/src/component/Log/index.tsx b/src/component/Log/index.tsx index 5c274309b..0fa180a83 100644 --- a/src/component/Log/index.tsx +++ b/src/component/Log/index.tsx @@ -248,7 +248,7 @@ const Log: React.FC = ({ if (!lineWrapNode) { index = copyData.current.scrollDirection === 'up' ? 0 : logData.data.length; } else { - index = Number(lineWrapNode.firstChild.innerText); + index = Number((lineWrapNode.firstChild as HTMLElement).innerText); } return index; }; diff --git a/src/d.ts/datasource.ts b/src/d.ts/datasource.ts index bbe587879..bed98e60c 100644 --- a/src/d.ts/datasource.ts +++ b/src/d.ts/datasource.ts @@ -55,3 +55,10 @@ export enum IDataSourceType { HUAWEI = 'HUAWEI', AWSS3 = 'AWSS3', } + +export enum IForbiddenSQLConsoleDataSourceType { + OSS = 'OSS', + COS = 'COS', + AWS = 'AWS', + OBS = 'OBS', +} diff --git a/src/page/Console/components/RecentlyDatabase/index.less b/src/page/Console/components/RecentlyDatabase/index.less index 2940b387c..bba49e8f2 100644 --- a/src/page/Console/components/RecentlyDatabase/index.less +++ b/src/page/Console/components/RecentlyDatabase/index.less @@ -2,14 +2,15 @@ margin-top: 16px; overflow: auto; cursor: default; - .action, - .disabledAction { + .action { cursor: pointer; font-size: 12px; color: var(--text-color-link); line-height: 20px; } .disabledAction { + font-size: 12px; + line-height: 20px; filter: grayscale(1); color: #00000040; } diff --git a/src/page/Console/components/RecentlyDatabase/index.tsx b/src/page/Console/components/RecentlyDatabase/index.tsx index 8dbc9bf2d..d2c70f078 100644 --- a/src/page/Console/components/RecentlyDatabase/index.tsx +++ b/src/page/Console/components/RecentlyDatabase/index.tsx @@ -26,11 +26,15 @@ import { getRecentlyDatabaseOperation } from './help'; import LogicDatabaseAsyncTask from '@/component/Task/LogicDatabaseAsyncTask'; import LogicIcon from '@/component/logicIcon'; import HelpDoc from '@/component/helpDoc'; +import { IForbiddenSQLConsoleDataSourceType } from '@/d.ts/datasource'; interface IProps { modalStore?: ModalStore; } - +enum ETootipType { + DATABASE = 'database', + PROJECT = 'project', +} const RecentlyDatabase: React.FC = ({ modalStore }) => { const { data: databaseList, @@ -62,7 +66,7 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { const renderTooltipContent = ({ type, record }) => { switch (type) { - case 'project': + case ETootipType.PROJECT: return (
{formatMessage( @@ -88,7 +92,7 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => {
); - case 'database': + case ETootipType.DATABASE: return (
{formatMessage({ @@ -126,20 +130,39 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { ellipsis: true, width: columnWidth[index], render: (value, record) => { - const hasProjectAuth = record?.project?.currentUserResourceRoles?.length > 0; - const hasDBAuth = !!record?.authorizedPermissionTypes?.length; - const actionStyle = hasProjectAuth ? styles.action : styles.disabledAction; - const normalStyle = hasProjectAuth ? '' : styles.disabledAction; + const needToApplyProjectAuth = !record?.project?.currentUserResourceRoles?.length; + const needToApplyDatabaseAuth = !record?.authorizedPermissionTypes?.length; + + const forbiddenAccessSQLConsole = [ + IForbiddenSQLConsoleDataSourceType.OSS, + IForbiddenSQLConsoleDataSourceType.COS, + IForbiddenSQLConsoleDataSourceType.AWS, + IForbiddenSQLConsoleDataSourceType.OBS, + ].includes(record?.dataSource?.type); + + const disabledAllOperations = needToApplyProjectAuth || forbiddenAccessSQLConsole; + + const recordWithActionClassName = disabledAllOperations + ? styles.disabledAction + : styles.action; + + const recordWithoutActionClassName = disabledAllOperations ? styles.disabledAction : ''; + switch (key) { case EDatabaseTableColumnKey.Operation: const operation = getRecentlyDatabaseOperation({ record, project: record?.project }); return (
{operation.map((item, index) => { + if (disabledAllOperations || needToApplyDatabaseAuth) { + item.disable = true; + } return renderTool(item, index); })} @@ -150,67 +173,76 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { const databaseStyle = getDataSourceStyleByConnectType(record?.dataSource?.type); const existed = record?.existed; return ( -
- - { - if (!existed) { - return; - } - gotoSQLWorkspace( - record?.project?.id, - record?.dataSource?.id, - record?.id, - null, - '', - isLogicalDatabase(record), - ); - }} - > - {value} - {!existed && ( - - )} - - - } - icon={ - record?.type === 'LOGICAL' ? ( -
- -
- ) : ( - - ) + +
-
+ > + + { + if (!existed || disabledAllOperations || needToApplyDatabaseAuth) { + return; + } + gotoSQLWorkspace( + record?.project?.id, + record?.dataSource?.id, + record?.id, + null, + '', + isLogicalDatabase(record), + ); + }} + > + {value} + {!existed && ( + + )} + +
+ } + icon={ + record?.type === 'LOGICAL' ? ( +
+ +
+ ) : ( + + ) + } + /> +
+ ); case EDatabaseTableColumnKey.DataSource: @@ -218,9 +250,9 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { if (!value) { return ( @@ -230,14 +262,18 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { } return ( -
+
@@ -261,8 +297,11 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { case EDatabaseTableColumnKey.Project: return (
{ + if (disabledAllOperations) { + return; + } window.open(`#/project/${value.id}/database`); }} > From cb2d9a79544fba419784cd8274585feb9319a344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Wed, 6 Aug 2025 13:57:23 +0800 Subject: [PATCH 005/239] =?UTF-8?q?PullRequest:=20919=20feat:=20=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E6=A0=91=E3=80=81=E5=BA=93=E9=80=89=E6=8B=A9=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=8C=89=E7=A7=9F=E6=88=B7=E5=88=86=E7=BB=84=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E5=B1=82=E7=BA=A7=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=BA=90=E5=B1=82=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/tenantGroup of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/919 Reviewed-by: 晓康 * feat: 资源树、库选择组件按租户分组调整层级,增加数据源层级 --- .../ResourceTree/DatabaseTree/index.tsx | 7 ++++++- .../ResourceTree/DatabaseTree/useGroupData.ts | 21 ++++++++++++++----- .../Workspace/SideBar/ResourceTree/const.ts | 6 +++--- .../Workspace/SideBar/ResourceTree/index.tsx | 8 +++---- .../SessionSelect/SessionDropdown/helper.tsx | 11 ++++++---- .../SessionSelect/SessionDropdown/index.tsx | 6 +++--- src/page/Workspace/index.tsx | 1 - 7 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/index.tsx b/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/index.tsx index 7fcfc8490..a555ff5f4 100644 --- a/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/index.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/index.tsx @@ -96,7 +96,12 @@ const DatabaseTree = function () { const group = DatabaseGroupMap?.[item]?.entries()?.next()?.value?.[1]; defaultExpandedKeys.push(getGroupKey(group?.mapId, item)); if ( - [DatabaseGroup.cluster, DatabaseGroup.environment, DatabaseGroup.connectType].includes(item) + [ + DatabaseGroup.cluster, + DatabaseGroup.environment, + DatabaseGroup.connectType, + DatabaseGroup.tenant, + ].includes(item) ) { const secondGroup = group?.secondGroup?.entries()?.next()?.value?.[1]; defaultExpandedKeys.push(getSecondGroupKey(group?.mapId, secondGroup?.mapId, item)); diff --git a/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/useGroupData.ts b/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/useGroupData.ts index 1dbf985e4..298650d96 100644 --- a/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/useGroupData.ts +++ b/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/useGroupData.ts @@ -23,7 +23,7 @@ const useGroupData = (props: IProps) => { const datasourceGruop: Map = new Map(); const clusterGroup: Map = new Map(); const projectGroup: Map = new Map(); - const tenantGroup: Map = new Map(); + const tenantGroup: Map = new Map(); const allDatabases: Map = new Map(); const filteredList = filter ? databaseList?.filter(filter) : databaseList; const allDatasources: IConnection[] = []; @@ -152,19 +152,30 @@ const useGroupData = (props: IProps) => { // 租户分组 { const { mapId, groupName, tip } = getMapIdByDB(db, DatabaseGroup.tenant); - const tenantDatabases: GroupWithDatabases[DatabaseGroup.tenant] = tenantGroup.get( + const tenantDatabases: GroupWithSecondGroup[DatabaseGroup.tenant] = tenantGroup.get( mapId, ) || { groupName, mapId, tip, - databases: [], + secondGroup: new Map(), }; + const { mapId: secondGroupMapId, groupName: secondGroupgroupName } = getMapIdByDB( + db, + DatabaseGroup.dataSource, + ); + const secondGroupDatabase: GroupWithDatabases[DatabaseGroup.dataSource] = + tenantDatabases.secondGroup.get(secondGroupMapId) || { + databases: [], + groupName: secondGroupgroupName, + mapId: secondGroupMapId, + }; if (db.type === 'LOGICAL') { - tenantDatabases.databases.unshift(db); + secondGroupDatabase.databases.unshift(db); } else { - tenantDatabases.databases.push(db); + secondGroupDatabase.databases.push(db); } + tenantDatabases.secondGroup.set(secondGroupMapId, secondGroupDatabase); tenantGroup.set(mapId, tenantDatabases); } }); diff --git a/src/page/Workspace/SideBar/ResourceTree/const.ts b/src/page/Workspace/SideBar/ResourceTree/const.ts index 1ea56c15f..11252590a 100644 --- a/src/page/Workspace/SideBar/ResourceTree/const.ts +++ b/src/page/Workspace/SideBar/ResourceTree/const.ts @@ -107,7 +107,7 @@ const getShouldExpandedGroupKeys = (params: { getGroupKey(mapId, groupMode), getSecondGroupKey(mapId, secondMapId, groupMode), ); - if ([DatabaseGroup.project, DatabaseGroup.dataSource, DatabaseGroup.tenant].includes(groupMode)) { + if ([DatabaseGroup.project, DatabaseGroup.dataSource].includes(groupMode)) { shouldExpandedKeys = shouldExpandedKeys.filter((item) => { if (isString(item)) { return !item.includes(TreeDataSecondGroupKey); @@ -225,7 +225,7 @@ const getShouldExpandedKeysByObject = (params: { break; } } - if ([DatabaseGroup.project, DatabaseGroup.dataSource, DatabaseGroup.tenant].includes(groupMode)) { + if ([DatabaseGroup.project, DatabaseGroup.dataSource].includes(groupMode)) { shouldExpandedKeys = shouldExpandedKeys.filter((item) => { if (isString(item)) { return !item?.includes(TreeDataSecondGroupKey); @@ -414,7 +414,7 @@ const getObjectShouldExpandedKeysByPage = (params: { break; } } - if ([DatabaseGroup.project, DatabaseGroup.dataSource, DatabaseGroup.tenant].includes(groupMode)) { + if ([DatabaseGroup.project, DatabaseGroup.dataSource].includes(groupMode)) { shouldExpandedKeys = shouldExpandedKeys.filter((item) => { if (isString(item)) { return !item?.includes(TreeDataSecondGroupKey); diff --git a/src/page/Workspace/SideBar/ResourceTree/index.tsx b/src/page/Workspace/SideBar/ResourceTree/index.tsx index 1becd1372..84db5ad48 100644 --- a/src/page/Workspace/SideBar/ResourceTree/index.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/index.tsx @@ -263,8 +263,7 @@ const ResourceTree: React.FC = function ({ }); } case DatabaseGroup.project: - case DatabaseGroup.dataSource: - case DatabaseGroup.tenant: { + case DatabaseGroup.dataSource: { return databases.map((groupItem) => { const groupKey = getGroupKey(groupItem.mapId, groupMode); let data, icon; @@ -276,7 +275,6 @@ const ResourceTree: React.FC = function ({ } return { title: groupItem.groupName, - tip: groupItem.tip, key: groupKey, type: GroupNodeToResourceNodeType[groupMode], data: data ?? null, @@ -306,11 +304,13 @@ const ResourceTree: React.FC = function ({ } case DatabaseGroup.cluster: case DatabaseGroup.environment: - case DatabaseGroup.connectType: { + case DatabaseGroup.connectType: + case DatabaseGroup.tenant: { return databases.map((groupItem) => { const groupKey = getGroupKey(groupItem.mapId, groupMode); return { title: groupItem.groupName, + tip: groupItem.tip, key: groupKey, type: GroupNodeToResourceNodeType[groupMode], children: [...groupItem.secondGroup.values()]?.map((sItem) => { diff --git a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/helper.tsx b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/helper.tsx index 3fd8e5087..441f66916 100644 --- a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/helper.tsx +++ b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/helper.tsx @@ -58,9 +58,12 @@ const DatabaseGroupArr = [ ]; const hasSecondGroup = (group: DatabaseGroup) => { - return [DatabaseGroup.cluster, DatabaseGroup.environment, DatabaseGroup.connectType].includes( - group, - ); + return [ + DatabaseGroup.cluster, + DatabaseGroup.environment, + DatabaseGroup.connectType, + DatabaseGroup.tenant, + ].includes(group); }; const getShouldExpandedGroupKeys = (params: { @@ -96,7 +99,7 @@ const getShouldExpandedGroupKeys = (params: { getGroupKey(mapId, groupMode), getSecondGroupKey(mapId, secondMapId, groupMode), ); - if ([DatabaseGroup.project, DatabaseGroup.dataSource, DatabaseGroup.tenant].includes(groupMode)) { + if ([DatabaseGroup.project, DatabaseGroup.dataSource].includes(groupMode)) { shouldExpandedKeys = shouldExpandedKeys.filter((item) => { if (isString(item)) { return !item.includes(TreeDataSecondGroupKey); diff --git a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/index.tsx b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/index.tsx index ad26614b7..04d4c9098 100644 --- a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/index.tsx +++ b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/index.tsx @@ -384,8 +384,7 @@ const SessionDropdown: React.FC = (props) => { break; } case DatabaseGroup.project: - case DatabaseGroup.dataSource: - case DatabaseGroup.tenant: { + case DatabaseGroup.dataSource: { _treeData = [...(DatabaseGroupMap[groupMode]?.values() || [])].map((groupItem) => { const groupKey = getGroupKey(groupItem.mapId, groupMode); return { @@ -412,7 +411,8 @@ const SessionDropdown: React.FC = (props) => { } case DatabaseGroup.cluster: case DatabaseGroup.environment: - case DatabaseGroup.connectType: { + case DatabaseGroup.connectType: + case DatabaseGroup.tenant: { _treeData = [...(DatabaseGroupMap[groupMode]?.values() || [])].map((groupItem) => { const groupKey = getGroupKey(groupItem.mapId, groupMode); return { diff --git a/src/page/Workspace/index.tsx b/src/page/Workspace/index.tsx index 9761595c2..e5a1f220d 100644 --- a/src/page/Workspace/index.tsx +++ b/src/page/Workspace/index.tsx @@ -50,7 +50,6 @@ import GlobalModals from './GlobalModals'; import WorkBenchLayout from './Layout'; import SideBar from './SideBar'; import { isLogicalDatabase } from '@/util/database'; -import { DatabaseGroup } from '@/d.ts/database'; import { ResourceNodeType } from '@/page/Workspace/SideBar/ResourceTree/type'; import { getAsyncResultSet } from '@/common/network/task'; From 89ba39f29dccde0538b4b2379df0a3851c4beccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Mon, 11 Aug 2025 10:26:54 +0800 Subject: [PATCH 006/239] =?UTF-8?q?PullRequest:=20925=20feat:=20=E5=8F=AA?= =?UTF-8?q?=E5=9C=A8=E4=B8=93=E6=9C=89=E4=BA=91=E5=B1=95=E7=A4=BA=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E6=96=B0=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/docs of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/925 Reviewed-by: 晓康 * feat: 只在专有云展示下载新版 --- src/page/Console/{const.ts => const.tsx} | 24 +++++++++++++++++++----- src/page/Console/index.less | 1 - src/page/Console/index.tsx | 18 ++++++------------ src/plugins/defaultConfig.ts | 1 + 4 files changed, 26 insertions(+), 18 deletions(-) rename src/page/Console/{const.ts => const.tsx} (87%) diff --git a/src/page/Console/const.ts b/src/page/Console/const.tsx similarity index 87% rename from src/page/Console/const.ts rename to src/page/Console/const.tsx index 93e2b02b1..f955e8d5b 100644 --- a/src/page/Console/const.ts +++ b/src/page/Console/const.tsx @@ -1,6 +1,9 @@ import { formatMessage } from '@/util/intl'; import { TaskPageType } from '@/d.ts'; -import { title } from 'process'; +import { ReactComponent as DownloadSvg } from '@/svgr/download-fill.svg'; +import { ReactComponent as GithubSvg } from '@/svgr/github.svg'; +import { ReactComponent as SendSvg } from '@/svgr/send-fill.svg'; +import Icon from '@ant-design/icons'; export enum EQuickStartRole { Admin, @@ -105,11 +108,22 @@ export const ConsoleTextConfig = { }, aboutUs: { helps: [ - formatMessage({ id: 'src.page.Console.D66A7480', defaultMessage: '下载新版' }), - formatMessage({ id: 'src.page.Console.B27ADAC6', defaultMessage: '产品动态' }), - formatMessage({ id: 'src.page.Console.9B6E647E', defaultMessage: '反馈建议' }), + { + title: formatMessage({ id: 'src.page.Console.D66A7480', defaultMessage: '下载新版' }), + url: 'https://www.oceanbase.com/download', + icon: , + }, + { + title: formatMessage({ id: 'src.page.Console.B27ADAC6', defaultMessage: '产品动态' }), + url: 'releaseNote', + icon: , + }, + { + title: formatMessage({ id: 'src.page.Console.9B6E647E', defaultMessage: '反馈建议' }), + url: 'issues', + icon: , + }, ], - urlKeys: ['softwarecenter', 'releaseNote', 'issues'], QRUrl: 'https://qr.dingtalk.com/action/joingroup?code=v1,k1,HovdSAqfBdRGqRk2jQ0TDu1eMvQ+BB6rt8mFHeIqi/A=&_dt_no_comment=1&origin=11', }, diff --git a/src/page/Console/index.less b/src/page/Console/index.less index 4f80a46b1..4a0ee84fc 100644 --- a/src/page/Console/index.less +++ b/src/page/Console/index.less @@ -280,7 +280,6 @@ } .aboutUsContent { display: flex; - align-items: center; justify-content: space-between; } .article { diff --git a/src/page/Console/index.tsx b/src/page/Console/index.tsx index 703bd2c76..c3d42cb0b 100644 --- a/src/page/Console/index.tsx +++ b/src/page/Console/index.tsx @@ -4,9 +4,6 @@ import { useMount, useRequest } from 'ahooks'; import modal from '@/store/modal'; import { Card, Col, Divider, Popconfirm, Radio, Row, Spin, Tooltip, Typography } from 'antd'; import odc from '@/plugins/odc'; -import { ReactComponent as DownloadSvg } from '@/svgr/download-fill.svg'; -import { ReactComponent as GithubSvg } from '@/svgr/github.svg'; -import { ReactComponent as SendSvg } from '@/svgr/send-fill.svg'; import { getImg } from '@/util/intl'; import Icon, { ExperimentOutlined } from '@ant-design/icons'; @@ -30,12 +27,6 @@ const paddingCal = (currentLayout) => { return currentLayout === gridConfig.all ? 0 : 8; }; -const aboutUsIcons = [ - , - , - , -]; - const Console = () => { const { quickStart, aboutUs, bestPractice, schdules } = ConsoleTextConfig; const [currentQuickStartRole, setCurrentQuickStartRole] = useState(EQuickStartRole.Admin); @@ -368,19 +359,22 @@ const Console = () => {
{aboutUs.helps.map((help, index) => { + if (!odc.appConfig.canDownloadNewVersion && index === 0) { + return null; + } return (
{ - window.open(getOBDocsUrl(aboutUs.urlKeys[index])); + window.open(getOBDocsUrl(help.url)); }} > - {help} + {help.title}
} /> diff --git a/src/plugins/defaultConfig.ts b/src/plugins/defaultConfig.ts index 69cd4bb3a..0998f614d 100644 --- a/src/plugins/defaultConfig.ts +++ b/src/plugins/defaultConfig.ts @@ -78,6 +78,7 @@ export default { connection: { sys: true, }, + canDownloadNewVersion: true, task: { sys: true, isSupportTaksImport: true, From 3d069baf4c24f6b6e2c22cf426fed30438ab3998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Mon, 11 Aug 2025 13:57:27 +0800 Subject: [PATCH 007/239] PullRequest: 927 feat: Synchronize 440 iterations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'temp/mergeTo441 of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/927 Reviewed-by: 晓康 * PullRequest: 902 补充索引名称限制输入空格 * PullRequest: 904 SQL结果集Tab管理与锁定功能优化 * PullRequest: 906 fix: 资源树数据源名称太长搜索按钮被挡住的问题 * PullRequest: 907 fix: 440bugfix * PullRequest: 897 feat: 数据归档/数据清理增加配置项 * PullRequest: 908 修正 addOperations 逻辑判定 * PullRequest: 909 按实际顺序展示 columnText * PullRequest: 910 按 sqlId 判定,避免结果重复渲染 * PullRequest: 912 feat: 适配后端数据库列表查询优化 * feat: add windows sign tool * fix: add publishername * PullRequest: 915 feat: 修复个人空间数据库列表权限bug * PullRequest: 918 fix: 440bugfix * PullRequest: 922 fix: 风险识别规则bug修复 * PullRequest: 923 440bugfix * PullRequest: 924 fix: 库选择组件,工单详情样式优化 * PullRequest: 926 fix: 补充数值校验 --- build/electron-build.config.js | 6 +- package.json | 2 +- pnpm-lock.yaml | 203 +++++++++++------- scripts/client/winsign.js | 13 ++ src/common/datasource/oceanbase/obmysql.ts | 1 + src/common/network/database.ts | 2 + src/common/network/sessionParams.ts | 2 +- src/component/CommonTable/index.tsx | 2 +- src/component/Crontab/utils.ts | 8 +- src/component/EllipsisText/index.less | 5 + src/component/EllipsisText/index.tsx | 31 +++ src/component/Log/index.tsx | 2 +- .../Task/component/ActionBar/index.tsx | 72 +++---- .../component/DataTransferModal/index.tsx | 9 +- .../Task/component/DatabaseLabel/index.tsx | 22 +- .../component/ShardingStrategyItem/index.tsx | 22 +- src/component/Task/component/Status/index.tsx | 4 + .../Task/component/TableSelecter/index.tsx | 8 +- .../Task/component/TableSelecter/interface.ts | 2 + src/component/Task/helper.tsx | 43 ++++ .../AlterDdlTask/DetailContent/index.tsx | 5 +- .../CreateModal/index.tsx | 4 +- .../CreateModal/utils.tsx | 24 --- .../DetailContent/index.tsx | 6 +- .../ApplyPermission/DetailContent/index.tsx | 3 +- .../CreateModal/index.tsx | 44 ++-- .../DetailContent/index.tsx | 6 +- .../modals/AsyncTask/DetailContent/index.tsx | 3 +- .../DataArchiveTask/CreateModal/index.tsx | 2 +- .../DataClearTask/CreateModal/index.tsx | 2 +- .../DataMockerTask/DetailContent/index.tsx | 3 +- .../CreateModal/ExportForm/index.tsx | 12 +- .../modals/ExportTask/CreateModal/index.tsx | 2 +- .../DetailContent/index.tsx | 5 +- .../MutipleAsyncTask/DetailContent/index.tsx | 3 +- .../PartitionTask/DetailContent/index.tsx | 3 +- .../DetailContent/index.tsx | 3 +- .../SQLPlanTask/DetailContent/index.tsx | 3 +- .../ShadowSyncTask/DetailContent/index.tsx | 3 +- .../DetailContent/index.tsx | 11 +- src/component/helpDoc/doc.tsx | 2 + src/d.ts/index.ts | 4 +- .../components/ChangeOwnerModal/index.tsx | 16 +- src/page/Project/Database/hooks/useData.ts | 1 + .../ManageModal/Database/CreateAuth/index.tsx | 2 +- .../Database/TaskApplyList/index.tsx | 2 +- .../Database/UserAuthList/index.tsx | 2 +- .../ManageModal/Table/CreateAuth/index.tsx | 3 +- .../ManageModal/Table/TaskApplyList/index.tsx | 2 +- .../ManageModal/Table/UserAuthList/index.tsx | 2 +- .../RiskLevel/components/InnerRiskLevel.tsx | 44 ++-- .../DatabaseSearchModal/components/List.tsx | 1 + .../TreeNodeMenu/config/database.tsx | 8 +- .../ResourceTree/TreeNodeMenu/dataSource.tsx | 5 +- .../ResourceTree/TreeNodeMenu/index.less | 20 ++ .../CreateTable/Columns/columns.tsx | 5 +- .../CreateTable/TableIndex/columns.tsx | 3 +- .../components/DDLResultSet/StatusBar.tsx | 21 +- .../components/DDLResultSet/index.tsx | 20 +- .../EditableTable/Editors/NoSpaceEditor.tsx | 116 ++++++++++ .../Workspace/components/SQLPage/index.tsx | 2 +- .../SessionSelect/SelectItem.tsx | 39 ++-- .../SessionSelect/SessionDropdown/index.tsx | 9 +- .../SessionDropdown/renderDatabaseNode.tsx | 4 + .../SessionSelect/index.less | 14 ++ .../SessionManagementPage/index.tsx | 13 +- src/page/Workspace/context/WorkspaceStore.tsx | 21 +- src/store/sql/index.tsx | 125 +++++++---- src/util/utils.ts | 4 + 69 files changed, 778 insertions(+), 338 deletions(-) create mode 100644 scripts/client/winsign.js create mode 100644 src/component/EllipsisText/index.less create mode 100644 src/component/EllipsisText/index.tsx create mode 100644 src/page/Workspace/components/EditableTable/Editors/NoSpaceEditor.tsx diff --git a/build/electron-build.config.js b/build/electron-build.config.js index 9edb9b46a..dc6f408d8 100644 --- a/build/electron-build.config.js +++ b/build/electron-build.config.js @@ -21,9 +21,13 @@ const config = { writeUpdateInfo: false }, win: { + publisherName: 'OceanBase', target: 'nsis', rfc3161TimeStampServer: "http://sha256timestamp.ws.symantec.com/sha256/timestamp", - signingHashAlgorithms: ["sha256"] + signingHashAlgorithms: ["sha256"], + signtoolOptions: { + sign: "./scripts/client/winsign.js" + } }, nsis: { differentialPackage: false, diff --git a/package.json b/package.json index 70aba7535..7d1c2d913 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "tar": "^6.1.11", "ts-is-present": "^1.1.3", "ts-loader": "8.4.0", - "typescript": "^4.0.0", + "typescript": "^5.0.0", "webpack": "^4.28.0", "webpack-cli": "^3.2.1", "webpack-merge": "^4.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 379e3f730..4dd806567 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,7 +56,7 @@ importers: version: 1.5.3(monaco-editor@0.36.1) '@oceanbase-odc/ob-intl-cli': specifier: ^2.1.3 - version: 2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@4.9.5) + version: 2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.8.3) '@oceanbase-odc/ob-parser-js': specifier: ^3.1.2 version: 3.1.2 @@ -107,7 +107,7 @@ importers: version: 3.0.0 '@umijs/max': specifier: ^4.0.66 - version: 4.4.11(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0) + version: 4.4.11(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) adm-zip: specifier: ^0.5.5 version: 0.5.16 @@ -278,7 +278,7 @@ importers: version: 17.0.2(react@17.0.2) react-intl: specifier: ^5.20.10 - version: 5.25.1(react@17.0.2)(typescript@4.9.5) + version: 5.25.1(react@17.0.2)(typescript@5.8.3) react-resizable: specifier: ^1.10.1 version: 1.11.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -311,10 +311,10 @@ importers: version: 1.2.2 ts-loader: specifier: 8.4.0 - version: 8.4.0(typescript@4.9.5)(webpack@4.47.0) + version: 8.4.0(typescript@5.8.3)(webpack@4.47.0) typescript: - specifier: ^4.0.0 - version: 4.9.5 + specifier: ^5.0.0 + version: 5.8.3 webpack: specifier: ^4.28.0 version: 4.47.0(webpack-cli@3.3.12) @@ -1902,42 +1902,49 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.0.1': resolution: {integrity: sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.0.1': resolution: {integrity: sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.0.1': resolution: {integrity: sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.0.1': resolution: {integrity: sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.0.1': resolution: {integrity: sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.0.1': resolution: {integrity: sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/nice-win32-arm64-msvc@1.0.1': resolution: {integrity: sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==} @@ -2329,24 +2336,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.12.4': resolution: {integrity: sha512-wE5jmFi5cEQyLy8WmCWmNwfKETrnzy2D8YNi/xpYWpLPWqPhcelpa6tswkfYlbsMmmOh7hQNoTba1QdGu0jvHQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.12.4': resolution: {integrity: sha512-6S50Xd/7ePjEwrXyHMxpKTZ+KBrgUwMA8hQPbArUOwH4S5vHBr51heL0iXbUkppn1bkSr0J0IbOove5hzn+iqQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.12.4': resolution: {integrity: sha512-hbYRyaHhC13vYKuGG5BrAG5fjjWEQFfQetuFp/4QKEoXDzdnabJoixxWTQACDL3m0JW32nJ+gUzsYIPtFYkwXg==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.12.4': resolution: {integrity: sha512-e6EbfjPL8GA/bb1lc9Omtxjlz+1ThTsAuBsy4Q3Kpbuh6B3jclg8KzxU/6t91v23wG593mieTyR5f3Pr7X3AWw==} @@ -2899,24 +2910,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@umijs/es-module-parser-linux-arm64-musl@0.0.7': resolution: {integrity: sha512-cqQffARWkmQ3n1RYNKZR3aD6X8YaP6u1maASjDgPQOpZMAlv/OSDrM/7iGujWTs0PD0haockNG9/DcP6lgPHMw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@umijs/es-module-parser-linux-x64-gnu@0.0.7': resolution: {integrity: sha512-PHrKHtT665Za0Ydjch4ACrNpRU+WIIden12YyF1CtMdhuLDSoU6UfdhF3NoDbgEUcXVDX/ftOqmj0SbH3R1uew==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@umijs/es-module-parser-linux-x64-musl@0.0.7': resolution: {integrity: sha512-cyZvUK5lcECLWzLp/eU1lFlCETcz+LEb+wrdARQSST1dgoIGZsT4cqM1WzYmdZNk3o883tiZizLt58SieEiHBQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@umijs/es-module-parser-win32-arm64-msvc@0.0.7': resolution: {integrity: sha512-V7WxnUI88RboSl0RWLNQeKBT7EDW35fW6Tn92zqtoHHxrhAIL9DtDyvC8REP4qTxeZ6Oej/Ax5I6IjsLx3yTOg==} @@ -2961,24 +2976,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@umijs/mako-linux-arm64-musl@0.11.10': resolution: {integrity: sha512-kqI1Jw6IHtDwrcsqPZrYxsV3pHzZyOR+6fCFnF5MSURnXbUbJb6Rk66VsKKpMqbyfsEO6nt0WT9FrRBlFvRU2A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@umijs/mako-linux-x64-gnu@0.11.10': resolution: {integrity: sha512-jlhXVvWJuumMmiE3z3ViugOMx9ZasNM1anng0PsusCgDwfy0IOfGzfwfwagqtzfsC5MwyRcfnRQyDdbfbroaSA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@umijs/mako-linux-x64-musl@0.11.10': resolution: {integrity: sha512-SLV/PRdL12dFEKlQGenW3OboZXmdYi25y+JblgVJLBhpdxZrHFqpCsTZn4L3hVEhyl0/ksR1iY0wtfK3urR29g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@umijs/mako-win32-ia32-msvc@0.11.10': resolution: {integrity: sha512-quCWpVl7yQjG+ccGhkF81GxO3orXdPW1OZWXWxJgOI0uPk7Hczh2EYMEVqqQGbi/83eJ1e3iE1jRTl/+2eHryQ==} @@ -6483,24 +6502,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.22.1: resolution: {integrity: sha512-MCV6RuRpzXbunvzwY644iz8cw4oQxvW7oer9xPkdadYqlEyiJJ6wl7FyJOH7Q6ZYH4yjGAUCvxDBxPbnDu9ZVg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.22.1: resolution: {integrity: sha512-RjNgpdM20VUXgV7us/VmlO3Vn2ZRiDnc3/bUxCVvySZWPiVPprpqW/QDWuzkGa+NCUf6saAM5CLsZLSxncXJwg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.22.1: resolution: {integrity: sha512-ZgO4C7Rd6Hv/5MnyY2KxOYmIlzk4rplVolDt3NbkNR8DndnyX0Q5IR4acJWNTBICQ21j3zySzKbcJaiJpk/4YA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-x64-msvc@1.22.1: resolution: {integrity: sha512-4pozV4eyD0MDET41ZLHAeBo+H04Nm2UEYIk5w/ts40231dRFV7E0cjwbnZvSoc1DXFgecAhiC0L16ruv/ZDCpg==} @@ -12032,7 +12055,7 @@ snapshots: '@formatjs/intl-utils@2.3.0': {} - '@formatjs/intl@2.2.1(typescript@4.9.5)': + '@formatjs/intl@2.2.1(typescript@5.8.3)': dependencies: '@formatjs/ecma402-abstract': 1.11.4 '@formatjs/fast-memoize': 1.2.1 @@ -12042,7 +12065,7 @@ snapshots: intl-messageformat: 9.13.0 tslib: 2.8.1 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 '@gar/promisify@1.1.3': {} @@ -12354,7 +12377,7 @@ snapshots: comlink: 4.4.2 monaco-editor: 0.36.1 - '@oceanbase-odc/ob-intl-cli@2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@4.9.5)': + '@oceanbase-odc/ob-intl-cli@2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.8.3)': dependencies: '@babel/core': 7.27.4 '@babel/generator': 7.27.5 @@ -12380,8 +12403,8 @@ snapshots: google-translate-api-x: 10.7.2 lodash: 4.17.21 node-fetch: 2.6.7(encoding@0.1.13) - prettier-eslint: 16.4.2(typescript@4.9.5) - prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@4.9.5) + prettier-eslint: 16.4.2(typescript@5.8.3) + prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@5.8.3) prettier-plugin-packagejson: 2.5.15(prettier@2.8.8) transitivePeerDependencies: - '@swc/helpers' @@ -13267,10 +13290,10 @@ snapshots: '@types/node': 16.18.126 optional: true - '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5)': + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@4.9.5)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@5.8.3) '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@7.32.0)(typescript@4.9.5) '@typescript-eslint/utils': 5.62.0(eslint@7.32.0)(typescript@4.9.5) @@ -13286,22 +13309,22 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5)': + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@5.8.3) '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.35.0)(typescript@4.9.5) - '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/type-utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) debug: 4.4.1 eslint: 8.35.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 semver: 7.7.2 - tsutils: 3.21.0(typescript@4.9.5) + tsutils: 3.21.0(typescript@5.8.3) optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -13330,28 +13353,28 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5)': + '@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) debug: 4.4.1 eslint: 8.35.0 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@4.9.5)': + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.1 eslint: 8.57.1 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -13382,15 +13405,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@5.62.0(eslint@8.35.0)(typescript@4.9.5)': + '@typescript-eslint/type-utils@5.62.0(eslint@8.35.0)(typescript@5.8.3)': dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) - '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) debug: 4.4.1 eslint: 8.35.0 - tsutils: 3.21.0(typescript@4.9.5) + tsutils: 3.21.0(typescript@5.8.3) optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -13428,7 +13451,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@6.21.0(typescript@4.9.5)': + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.1 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.7.2 + tsutils: 3.21.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 @@ -13437,9 +13474,9 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.7.2 - ts-api-utils: 1.4.3(typescript@4.9.5) + ts-api-utils: 1.4.3(typescript@5.8.3) optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -13458,14 +13495,14 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@5.62.0(eslint@8.35.0)(typescript@4.9.5)': + '@typescript-eslint/utils@5.62.0(eslint@8.35.0)(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.35.0) '@types/json-schema': 7.0.15 '@types/semver': 7.7.0 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 - '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) eslint: 8.35.0 eslint-scope: 5.1.1 semver: 7.7.2 @@ -13515,10 +13552,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@umijs/bundler-mako@0.11.10(postcss@8.5.6)(typescript@4.9.5)(webpack@4.47.0)': + '@umijs/bundler-mako@0.11.10(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0)': dependencies: '@umijs/bundler-utils': 4.4.11 - '@umijs/mako': 0.11.10(postcss@8.5.6)(typescript@4.9.5)(webpack@4.47.0) + '@umijs/mako': 0.11.10(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0) chalk: 4.1.2 compression: 1.8.0 connect-history-api-fallback: 2.0.0 @@ -13572,7 +13609,7 @@ snapshots: - supports-color - terser - '@umijs/bundler-webpack@4.4.11(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0)': + '@umijs/bundler-webpack@4.4.11(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0)': dependencies: '@svgr/core': 6.5.1 '@svgr/plugin-jsx': 6.5.1(@svgr/core@6.5.1) @@ -13587,7 +13624,7 @@ snapshots: cors: 2.8.5 css-loader: 6.7.1(webpack@4.47.0) es5-imcompatible-versions: 0.1.90 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@4.9.5)(webpack@4.47.0) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.3)(webpack@4.47.0) jest-worker: 29.4.3 lightningcss: 1.22.1 node-libs-browser: 2.2.1 @@ -13665,14 +13702,14 @@ snapshots: '@babel/preset-env': 7.27.2(@babel/core@7.27.4) '@babel/preset-react': 7.27.1(@babel/core@7.27.4) '@babel/preset-typescript': 7.27.1(@babel/core@7.27.4) - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@4.9.5) '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@4.9.5) chalk: 4.1.2 eslint: 7.32.0 eslint-config-prettier: 8.10.0(eslint@7.32.0) eslint-formatter-pretty: 4.1.0 eslint-plugin-babel: 5.3.1(eslint@7.32.0) - eslint-plugin-jest: 24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5) + eslint-plugin-jest: 24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@4.9.5) eslint-plugin-promise: 6.6.0(eslint@7.32.0) eslint-plugin-react: 7.37.5(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.2(eslint@7.32.0) @@ -13701,15 +13738,15 @@ snapshots: '@babel/runtime': 7.23.6 query-string: 6.14.1 - '@umijs/lint@4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.9.5)': + '@umijs/lint@4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.8.3)': dependencies: '@babel/core': 7.23.6 '@babel/eslint-parser': 7.23.3(@babel/core@7.23.6)(eslint@8.35.0) '@stylelint/postcss-css-in-js': 0.38.0(postcss-syntax@0.36.2(postcss@8.5.6))(postcss@8.5.6) - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5) - '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@5.8.3) '@umijs/babel-preset-umi': 4.4.11 - eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5) + eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3) eslint-plugin-react: 7.33.2(eslint@8.35.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.35.0) postcss: 8.5.6 @@ -13751,7 +13788,7 @@ snapshots: '@umijs/mako-win32-x64-msvc@0.11.10': optional: true - '@umijs/mako@0.11.10(postcss@8.5.6)(typescript@4.9.5)(webpack@4.47.0)': + '@umijs/mako@0.11.10(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0)': dependencies: '@module-federation/webpack-bundler-runtime': 0.8.12 '@swc/helpers': 0.5.1 @@ -13765,7 +13802,7 @@ snapshots: lodash: 4.17.21 node-libs-browser-okam: 2.2.5 piscina: 4.9.2 - postcss-loader: 8.1.1(postcss@8.5.6)(typescript@4.9.5)(webpack@4.47.0) + postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0) react-error-overlay: 6.0.9 react-refresh: 0.14.2 resolve: 1.22.10 @@ -13790,14 +13827,14 @@ snapshots: - typescript - webpack - '@umijs/max@4.4.11(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0)': + '@umijs/max@4.4.11(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0)': dependencies: - '@umijs/lint': 4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.9.5) + '@umijs/lint': 4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.8.3) '@umijs/plugins': 4.4.11(@babel/core@7.27.4)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(antd@4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(rc-field-form@2.7.0(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) antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) eslint: 8.35.0 stylelint: 14.8.2 - umi: 4.4.11(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0) + umi: 4.4.11(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) transitivePeerDependencies: - '@babel/core' - '@rspack/core' @@ -13896,17 +13933,17 @@ snapshots: - react-native - supports-color - '@umijs/preset-umi@4.4.11(@types/node@16.18.126)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0)': + '@umijs/preset-umi@4.4.11(@types/node@16.18.126)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0)': dependencies: '@iconify/utils': 2.1.1 '@svgr/core': 6.5.1 '@umijs/ast': 4.4.11 '@umijs/babel-preset-umi': 4.4.11 '@umijs/bundler-esbuild': 4.4.11 - '@umijs/bundler-mako': 0.11.10(postcss@8.5.6)(typescript@4.9.5)(webpack@4.47.0) + '@umijs/bundler-mako': 0.11.10(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0) '@umijs/bundler-utils': 4.4.11 '@umijs/bundler-vite': 4.4.11(@types/node@16.18.126)(lightningcss@1.22.1)(postcss@8.5.6)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1) - '@umijs/bundler-webpack': 4.4.11(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0) + '@umijs/bundler-webpack': 4.4.11(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) '@umijs/core': 4.4.11 '@umijs/did-you-know': 1.0.3 '@umijs/es-module-parser': 0.0.7 @@ -15523,14 +15560,14 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@9.0.0(typescript@4.9.5): + cosmiconfig@9.0.0(typescript@5.8.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 crc-32@1.2.2: {} @@ -16480,22 +16517,22 @@ snapshots: eslint: 7.32.0 eslint-rule-composer: 0.3.0 - eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5): + eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@4.9.5): dependencies: '@typescript-eslint/experimental-utils': 4.33.0(eslint@7.32.0)(typescript@4.9.5) eslint: 7.32.0 optionalDependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5): + eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3): dependencies: - '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) eslint: 8.35.0 optionalDependencies: - '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@4.9.5))(eslint@8.35.0)(typescript@4.9.5) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3) transitivePeerDependencies: - supports-color - typescript @@ -17145,7 +17182,7 @@ snapshots: forever-agent@0.6.1: {} - fork-ts-checker-webpack-plugin@8.0.0(typescript@4.9.5)(webpack@4.47.0): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.3)(webpack@4.47.0): dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -17159,7 +17196,7 @@ snapshots: schema-utils: 3.3.0 semver: 7.7.2 tapable: 2.2.2 - typescript: 4.9.5 + typescript: 5.8.3 webpack: 4.47.0(webpack-cli@3.3.12) form-data@2.3.3: @@ -19632,9 +19669,9 @@ snapshots: dependencies: postcss: 8.5.6 - postcss-loader@8.1.1(postcss@8.5.6)(typescript@4.9.5)(webpack@4.47.0): + postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0): dependencies: - cosmiconfig: 9.0.0(typescript@4.9.5) + cosmiconfig: 9.0.0(typescript@5.8.3) jiti: 1.21.7 postcss: 8.5.6 semver: 7.7.2 @@ -19826,9 +19863,9 @@ snapshots: prelude-ls@1.2.1: {} - prettier-eslint@16.4.2(typescript@4.9.5): + prettier-eslint@16.4.2(typescript@5.8.3): dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.3) common-tags: 1.8.2 dlv: 1.1.3 eslint: 8.57.1 @@ -19849,6 +19886,11 @@ snapshots: prettier: 2.8.8 typescript: 4.9.5 + prettier-plugin-organize-imports@3.2.4(prettier@2.8.8)(typescript@5.8.3): + dependencies: + prettier: 2.8.8 + typescript: 5.8.3 + prettier-plugin-packagejson@2.4.3(prettier@2.8.8): dependencies: sort-package-json: 2.4.1 @@ -20764,11 +20806,11 @@ snapshots: react: 17.0.2 shallow-equal: 1.2.1 - react-intl@5.25.1(react@17.0.2)(typescript@4.9.5): + react-intl@5.25.1(react@17.0.2)(typescript@5.8.3): dependencies: '@formatjs/ecma402-abstract': 1.11.4 '@formatjs/icu-messageformat-parser': 2.1.0 - '@formatjs/intl': 2.2.1(typescript@4.9.5) + '@formatjs/intl': 2.2.1(typescript@5.8.3) '@formatjs/intl-displaynames': 5.4.3 '@formatjs/intl-listformat': 6.5.3 '@types/hoist-non-react-statics': 3.3.6 @@ -20778,7 +20820,7 @@ snapshots: react: 17.0.2 tslib: 2.8.1 optionalDependencies: - typescript: 4.9.5 + typescript: 5.8.3 react-is@16.13.1: {} @@ -22288,20 +22330,20 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 - ts-api-utils@1.4.3(typescript@4.9.5): + ts-api-utils@1.4.3(typescript@5.8.3): dependencies: - typescript: 4.9.5 + typescript: 5.8.3 ts-is-present@1.2.2: {} - ts-loader@8.4.0(typescript@4.9.5)(webpack@4.47.0): + ts-loader@8.4.0(typescript@5.8.3)(webpack@4.47.0): dependencies: chalk: 4.1.2 enhanced-resolve: 4.5.0 loader-utils: 2.0.4 micromatch: 4.0.8 semver: 7.7.2 - typescript: 4.9.5 + typescript: 5.8.3 webpack: 4.47.0(webpack-cli@3.3.12) tslib@1.14.1: {} @@ -22315,6 +22357,11 @@ snapshots: tslib: 1.14.1 typescript: 4.9.5 + tsutils@3.21.0(typescript@5.8.3): + dependencies: + tslib: 1.14.1 + typescript: 5.8.3 + tsx@3.12.2: dependencies: '@esbuild-kit/cjs-loader': 2.4.4 @@ -22400,19 +22447,19 @@ snapshots: uc.micro@1.0.6: {} - umi@4.4.11(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0): + umi@4.4.11(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0): dependencies: '@babel/runtime': 7.23.6 '@umijs/bundler-utils': 4.4.11 - '@umijs/bundler-webpack': 4.4.11(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0) + '@umijs/bundler-webpack': 4.4.11(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) '@umijs/core': 4.4.11 - '@umijs/lint': 4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@4.9.5) - '@umijs/preset-umi': 4.4.11(@types/node@16.18.126)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@4.9.5)(webpack@4.47.0) + '@umijs/lint': 4.4.11(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.8.3) + '@umijs/preset-umi': 4.4.11(@types/node@16.18.126)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) '@umijs/renderer-react': 4.4.11(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@umijs/server': 4.4.11 '@umijs/test': 4.4.11(@babel/core@7.27.4) '@umijs/utils': 4.4.11 - prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@4.9.5) + prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@5.8.3) prettier-plugin-packagejson: 2.4.3(prettier@2.8.8) transitivePeerDependencies: - '@babel/core' diff --git a/scripts/client/winsign.js b/scripts/client/winsign.js new file mode 100644 index 000000000..0039f3b3d --- /dev/null +++ b/scripts/client/winsign.js @@ -0,0 +1,13 @@ +exports.default = async function(configuration) { + // do not include passwords or other sensitive data in the file + // rather create environment variables with sensitive data + const CONFIG_FILE = process.env.WINDOWS_SIGN_CONFIG_FILE; + + require("child_process").execSync( + // your commande here ! For exemple and with JSign : + ` smctl sign --keypair-alias key_1318155498 --config-file ${CONFIG_FILE} --input "${configuration.path}" -v`, + { + stdio: "inherit" + } + ); + }; \ No newline at end of file diff --git a/src/common/datasource/oceanbase/obmysql.ts b/src/common/datasource/oceanbase/obmysql.ts index ff9afc67d..8c3338386 100644 --- a/src/common/datasource/oceanbase/obmysql.ts +++ b/src/common/datasource/oceanbase/obmysql.ts @@ -121,6 +121,7 @@ const items: Record< sqlExplain: true, supportOBProxy: true, plRun: true, + plEdit: true, export: { fileLimit: true, snapshot: true, diff --git a/src/common/network/database.ts b/src/common/network/database.ts index bb113b769..c2b3f07da 100644 --- a/src/common/network/database.ts +++ b/src/common/network/database.ts @@ -33,6 +33,8 @@ interface listDatabasesParams { containsUnassigned?: boolean; existed?: boolean; includesPermittedAction?: boolean; + /** 是否查询数据库管理员owners列表,默认不查 */ + includesDbOwner?: boolean; type?: DBType[]; connectType?: ConnectType[]; dataSourceName?: string; diff --git a/src/common/network/sessionParams.ts b/src/common/network/sessionParams.ts index d3771c49f..efe9983a5 100644 --- a/src/common/network/sessionParams.ts +++ b/src/common/network/sessionParams.ts @@ -69,7 +69,7 @@ export async function killSessions( killType: 'session' | 'query', ): Promise< { - sessionId: number; + sessionId: string; killed: boolean; errorMessage?: string; }[] diff --git a/src/component/CommonTable/index.tsx b/src/component/CommonTable/index.tsx index 28e3762c9..6dd75fe2e 100644 --- a/src/component/CommonTable/index.tsx +++ b/src/component/CommonTable/index.tsx @@ -370,7 +370,7 @@ const CommonTable: ( }; }) : columns; - }, [enableResize, JSON.stringify(columns), columnWidthMap]); + }, [enableResize, columns, columnWidthMap]); function handleCloseAlert() { setAlertInfoVisible(false); diff --git a/src/component/Crontab/utils.ts b/src/component/Crontab/utils.ts index 5cfb901e6..48ece40e2 100644 --- a/src/component/Crontab/utils.ts +++ b/src/component/Crontab/utils.ts @@ -178,7 +178,11 @@ export const getCronString = (values: { dayOfMonth: number[]; }) => { const { hour, dayOfWeek, dayOfMonth } = values; - const initInterval = parser.parseExpression(initCronString); + let baseCron = initCronString; + if (dayOfWeek?.length > 0) { + baseCron = '0 0 0 ? * *'; + } + const initInterval = parser.parseExpression(baseCron); const fields = JSON.parse(JSON.stringify(initInterval.fields)); if (hour.length) { fields.hour = hour; @@ -186,7 +190,7 @@ export const getCronString = (values: { fields.second = [0]; } if (dayOfWeek.length) { - fields.dayOfWeek = dayOfWeek; + fields.dayOfWeek = dayOfWeek.map((d) => (d === 7 ? 0 : d)); } if (dayOfMonth.length) { fields.dayOfMonth = dayOfMonth; diff --git a/src/component/EllipsisText/index.less b/src/component/EllipsisText/index.less new file mode 100644 index 000000000..dce8bf4bc --- /dev/null +++ b/src/component/EllipsisText/index.less @@ -0,0 +1,5 @@ +.ellipsis { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/src/component/EllipsisText/index.tsx b/src/component/EllipsisText/index.tsx new file mode 100644 index 000000000..85156c5c7 --- /dev/null +++ b/src/component/EllipsisText/index.tsx @@ -0,0 +1,31 @@ +import styles from './index.less'; +import { Tooltip } from 'antd'; +import { useMemo } from 'react'; + +interface IProps { + content: string | React.ReactNode; + customTooltipContent?: React.ReactNode; + needTooltip?: boolean; +} + +const EllipsisText = (props: IProps) => { + const { content, customTooltipContent, needTooltip = true } = props; + + const tooltipContent = useMemo(() => { + if (!content || !needTooltip) return null; + if (customTooltipContent) { + return customTooltipContent; + } + return content; + }, [content, customTooltipContent, needTooltip]); + + return content ? ( + +
{content}
+
+ ) : ( + <>- + ); +}; + +export default EllipsisText; diff --git a/src/component/Log/index.tsx b/src/component/Log/index.tsx index 5c274309b..923e0569a 100644 --- a/src/component/Log/index.tsx +++ b/src/component/Log/index.tsx @@ -248,7 +248,7 @@ const Log: React.FC = ({ if (!lineWrapNode) { index = copyData.current.scrollDirection === 'up' ? 0 : logData.data.length; } else { - index = Number(lineWrapNode.firstChild.innerText); + index = Number((lineWrapNode.firstChild as HTMLElement)?.innerText); } return index; }; diff --git a/src/component/Task/component/ActionBar/index.tsx b/src/component/Task/component/ActionBar/index.tsx index c976191bd..56a6a816d 100644 --- a/src/component/Task/component/ActionBar/index.tsx +++ b/src/component/Task/component/ActionBar/index.tsx @@ -58,7 +58,6 @@ import type { UserStore } from '@/store/login'; import type { ModalStore } from '@/store/modal'; import type { SettingStore } from '@/store/setting'; import type { TaskStore } from '@/store/task'; -import taskStore from '@/store/task'; import ipcInvoke from '@/util/client/service'; import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; @@ -109,6 +108,7 @@ const ActionBar: React.FC = inject( disabledSubmit = false, result, delTaskList = [], + taskStore, setDelTaskList, } = props; /** 是否创建者 */ @@ -471,6 +471,36 @@ const ActionBar: React.FC = inject( ), }, + [TaskOperationType.PAUSE]: { + title: formatMessage( + { + id: 'src.component.Task.component.ActionBar.5495D4C7', + defaultMessage: '确认要禁用此{TaskTypeMapTaskType}?', + }, + { TaskTypeMapTaskType: taskTypeName }, + ), + content: ( + <> +
+ {formatMessage( + { + id: 'src.component.Task.component.ActionBar.EC0C09D6', + defaultMessage: '禁用{TaskTypeMapTaskType}', + }, + { TaskTypeMapTaskType: taskTypeName }, + )} +
+
+ { + formatMessage({ + id: 'odc.TaskManagePage.component.TaskTools.TheTaskNeedsToBe', + defaultMessage: '任务需要重新审批,审批通过后此任务将禁用', + }) /*任务需要重新审批,审批通过后此任务将禁用*/ + } +
+ + ), + }, [TaskOperationType.TERMINATE]: { title: formatMessage( { @@ -489,36 +519,6 @@ const ActionBar: React.FC = inject(
), - [TaskOperationType.PAUSE]: { - title: formatMessage( - { - id: 'src.component.Task.component.ActionBar.5495D4C7', - defaultMessage: '确认要禁用此{TaskTypeMapTaskType}?', - }, - { TaskTypeMapTaskType: taskTypeName }, - ), - content: ( - <> -
- {formatMessage( - { - id: 'src.component.Task.component.ActionBar.EC0C09D6', - defaultMessage: '禁用{TaskTypeMapTaskType}', - }, - { TaskTypeMapTaskType: taskTypeName }, - )} -
-
- { - formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.TheTaskNeedsToBe', - defaultMessage: '任务需要重新审批,审批通过后此任务将禁用', - }) /*任务需要重新审批,审批通过后此任务将禁用*/ - } -
- - ), - }, }, }; const { title, content } = config[operationType] || {}; @@ -586,8 +586,7 @@ const ActionBar: React.FC = inject( let tools = []; const addOperations = ({ auth, taskTypeLimit, operations }: IAddOperationsParams) => { const hasOperaitons = Boolean(operations?.length); - const useableTaskType = - (taskTypeLimit && taskTypeLimit.includes(task?.type)) || Boolean(taskTypeLimit?.length); + const useableTaskType = !taskTypeLimit || taskTypeLimit?.includes(task?.type); if (auth && useableTaskType && hasOperaitons) { tools.push(...operations); return true; @@ -864,7 +863,7 @@ const ActionBar: React.FC = inject( if (haveOperationPermission) { const taskRules = operationNeedPermission?.[status]?.taskRules; taskRules?.some((rule) => { - return addOperations(rule); + return addOperations(rule || {}); }); tools.push(...operationNeedPermission[status].operations); } @@ -1052,15 +1051,14 @@ const ActionBar: React.FC = inject( const addOperations = ({ taskTypeLimit, auth, operations }: IAddOperationsParams) => { const hasOperations = Boolean(operations?.length); - const useableTaskType = - (taskTypeLimit && taskTypeLimit.includes(task?.type)) || Boolean(taskTypeLimit?.length); + const useableTaskType = !taskTypeLimit || taskTypeLimit?.includes(task?.type); if (auth && hasOperations && useableTaskType) { tools.push(...operations); } }; const enableRetry = isOwner; initTools({ view: true, retry: enableRetry }); - addOperations(operationNeedPermission?.[status]); + addOperations(operationNeedPermission?.[status] || {}); switch (status) { case TaskStatus.APPROVING: { diff --git a/src/component/Task/component/DataTransferModal/index.tsx b/src/component/Task/component/DataTransferModal/index.tsx index 6a15469ac..7cce217b5 100644 --- a/src/component/Task/component/DataTransferModal/index.tsx +++ b/src/component/Task/component/DataTransferModal/index.tsx @@ -27,6 +27,8 @@ import styles from './index.less'; import ObjTable from './ObjTables'; import { getImportTypeLabel } from '@/component/Task/modals/ImportTask/CreateModal/ImportForm/helper'; import { getTaskExecStrategyMap } from '@/component/Task/const'; +import EllipsisText from '@/component/EllipsisText'; + const SimpleTextItem: React.FC<{ label: string; content: React.ReactNode; @@ -60,9 +62,10 @@ const SimpleTextItem: React.FC<{
{content} @@ -544,7 +547,7 @@ class TaskContent extends React.Component { id: 'odc.component.DataTransferModal.Database', defaultMessage: '所属数据库', })} - /*所属数据库*/ content={task?.database?.name || '-'} + /*所属数据库*/ content={} />
@@ -555,7 +558,7 @@ class TaskContent extends React.Component { defaultMessage: '所属数据源', }) /* 所属数据源 */ } - content={task?.database?.dataSource?.name || '-'} + content={} /> diff --git a/src/component/Task/component/DatabaseLabel/index.tsx b/src/component/Task/component/DatabaseLabel/index.tsx index 9b1c00171..3a33ced73 100644 --- a/src/component/Task/component/DatabaseLabel/index.tsx +++ b/src/component/Task/component/DatabaseLabel/index.tsx @@ -16,7 +16,7 @@ import RiskLevelLabel from '@/component/RiskLevelLabel'; import { IDatabase } from '@/d.ts/database'; -import { Space } from 'antd'; +import { Space, Tooltip } from 'antd'; import Icon from '@ant-design/icons'; import { getDataSourceStyleByConnectType } from '@/common/datasource'; import React from 'react'; @@ -30,7 +30,7 @@ const DatabaseLabel: React.FC = (props) => { const dbIcon = getDataSourceStyleByConnectType(database?.dataSource?.type)?.dbIcon; return ( - +
{!!database?.environment?.name && ( = (props) => { component={dbIcon?.component} style={{ fontSize: 16, marginRight: 4, verticalAlign: 'textBottom' }} /> - {database?.name || '-'} - +
+ +
+ {database?.name || '-'} +
+
+
+
); }; export default DatabaseLabel; diff --git a/src/component/Task/component/ShardingStrategyItem/index.tsx b/src/component/Task/component/ShardingStrategyItem/index.tsx index 5f766e51c..bd9737898 100644 --- a/src/component/Task/component/ShardingStrategyItem/index.tsx +++ b/src/component/Task/component/ShardingStrategyItem/index.tsx @@ -1,8 +1,13 @@ import { formatMessage } from '@/util/intl'; -import { Form, Radio } from 'antd'; +import { Form, Radio, Space } from 'antd'; import { ShardingStrategy } from '@/d.ts'; +import HelpDoc from '@/component/helpDoc'; export const shardingStrategyOptions = [ + { + label: '程序自动匹配', + value: ShardingStrategy.AUTO, + }, { label: formatMessage({ id: 'src.component.Task.component.ShardingStrategyItem.E5A6B481', @@ -22,10 +27,17 @@ export const shardingStrategyOptions = [ const ShardingStrategyItem = () => { return ( + + {formatMessage({ + id: 'src.component.Task.component.ShardingStrategyItem.3BD95C1A', + defaultMessage: '搜索策略', + })} + + +
+ } name="shardingStrategy" rules={[ { diff --git a/src/component/Task/component/Status/index.tsx b/src/component/Task/component/Status/index.tsx index e44245455..eec606bea 100644 --- a/src/component/Task/component/Status/index.tsx +++ b/src/component/Task/component/Status/index.tsx @@ -135,6 +135,10 @@ export const nodeStatus = { //已终止 status: 'error', }, + [TaskNodeStatus.EXECUTING_ABNORMAL]: { + text: '执行异常', + status: 'error', + }, [TaskNodeStatus.FAILED]: { text: formatMessage({ id: 'odc.component.TaskStatus.Failed.2', diff --git a/src/component/Task/component/TableSelecter/index.tsx b/src/component/Task/component/TableSelecter/index.tsx index d29f38a3c..16298582d 100644 --- a/src/component/Task/component/TableSelecter/index.tsx +++ b/src/component/Task/component/TableSelecter/index.tsx @@ -706,8 +706,14 @@ const TableSelecter: React.ForwardRefRenderFunction = return Array.from(new Set([...prevExpandKeys, databaseId])); }); }, + getAllLoadedTables: () => { + return databaseWithTableList.reduce( + (pre, cur) => pre.concat(cur.tableList, cur.viewList, cur.materializedViewList), + [], + ); + }, }), - [handleLoadTables], + [handleLoadTables, databaseWithTableList], ); useEffect(() => { diff --git a/src/component/Task/component/TableSelecter/interface.ts b/src/component/Task/component/TableSelecter/interface.ts index e5e82abdd..9a9996378 100644 --- a/src/component/Task/component/TableSelecter/interface.ts +++ b/src/component/Task/component/TableSelecter/interface.ts @@ -13,8 +13,10 @@ export interface TableSelecterRef { tables: LoadTableItems[]; externalTables: LoadTableItems[]; views: LoadTableItems[]; + materializedViews: LoadTableItems[]; }>; expandTable: (dbId: number) => void; + getAllLoadedTables: () => TableItemInDB[]; } export type LoadTableItems = { diff --git a/src/component/Task/helper.tsx b/src/component/Task/helper.tsx index 4c079fa4f..65b850e11 100644 --- a/src/component/Task/helper.tsx +++ b/src/component/Task/helper.tsx @@ -25,6 +25,7 @@ import { flatten } from 'lodash'; import type { Dayjs } from 'dayjs'; import { ITableLoadOptions } from '../CommonTable/interface'; export { TaskTypeMap } from '@/component/Task/component/TaskTable/const'; +import dayjs from 'dayjs'; // 423 屏蔽 SysFormItem 配置 export const ENABLED_SYS_FROM_ITEM = false; @@ -321,3 +322,45 @@ export const conditionExpressionColumns = [ }, }, ]; + +type TimeUnit = 'years' | 'months' | 'days'; + +const MAX_DATE = '9999-12-31 23:59:59'; +const MAX_DATE_LABEL = '9999-12-31'; + +/** + * 处理时间单位转换的兼容函数 + * @param value 时间值 + * @param unit 单位 + * @returns [转换后的值, 转换后的单位] + */ +const normalizeTimeUnit = (value: number, unit: TimeUnit): [number, TimeUnit] => { + if (unit === 'years' && value % 1 !== 0) { + // 处理年的小数情况,转换为月 + return [value * 12, 'months']; + } + return [value, unit]; +}; + +export const getExpireTime = (expireTime, customExpireTime, isCustomExpireTime) => { + if (isCustomExpireTime) { + return customExpireTime?.valueOf(); + } else { + const [offset, unit] = expireTime.split(',') ?? []; + if (offset === 'never') { + return dayjs(MAX_DATE)?.valueOf(); + } + const [normalizedValue, normalizedUnit] = normalizeTimeUnit(Number(offset), unit as TimeUnit); + return dayjs().add(normalizedValue, normalizedUnit)?.valueOf(); + } +}; + +export const getExpireTimeLabel = (expireTime) => { + const label = dayjs(expireTime).format('YYYY-MM-DD'); + return label === MAX_DATE_LABEL + ? formatMessage({ + id: 'src.component.Task.ApplyDatabasePermission.CreateModal.B5C7760D', + defaultMessage: '永不过期', + }) + : label; +}; diff --git a/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx b/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx index 5a583c82a..796dbc465 100644 --- a/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx +++ b/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx @@ -31,6 +31,7 @@ import { ClearStrategy, LockStrategy, LockStrategyLableMap } from '../CreateModa import { SwapTableType } from '../CreateModal/const'; import { ProjectRole } from '@/d.ts/project'; import userStore from '@/store/login'; +import EllipsisText from '@/component/EllipsisText'; const { Text } = Typography; interface IDDLAlterParamters { @@ -228,7 +229,7 @@ export function getItems( defaultMessage: '所属库', }), //所属库 - task?.database?.name || '-', + , ], [ @@ -237,7 +238,7 @@ export function getItems( defaultMessage: '所属数据源', }), //'所属数据源' - task?.database?.dataSource?.name || '-', + , ], [ formatMessage({ diff --git a/src/component/Task/modals/ApplyDatabasePermission/CreateModal/index.tsx b/src/component/Task/modals/ApplyDatabasePermission/CreateModal/index.tsx index e58cb50f3..64c9cce1f 100644 --- a/src/component/Task/modals/ApplyDatabasePermission/CreateModal/index.tsx +++ b/src/component/Task/modals/ApplyDatabasePermission/CreateModal/index.tsx @@ -40,8 +40,9 @@ import dayjs from 'dayjs'; import React, { useEffect, useState } from 'react'; import styles from './index.less'; import ProjectSelectEmpty from '@/component/Empty/ProjectSelectEmpty'; -import { getExpireTime, permissionOptions } from './utils'; +import { permissionOptions } from './utils'; import { expireTimeOptions, rules } from './const'; +import { getExpireTime } from '@/component/Task/helper'; export * from './const'; export * from './utils'; @@ -67,6 +68,7 @@ const CreateModal: React.FC = (props) => { const [confirmLoading, setConfirmLoading] = useState(false); const { run: getProjects, data: projects } = useRequest(listProjects, { defaultParams: [null, null, null], + manual: true, }); const projectOptions = projects?.contents?.map(({ name, id }) => ({ label: name, diff --git a/src/component/Task/modals/ApplyDatabasePermission/CreateModal/utils.tsx b/src/component/Task/modals/ApplyDatabasePermission/CreateModal/utils.tsx index 786159fc3..61779a5c6 100644 --- a/src/component/Task/modals/ApplyDatabasePermission/CreateModal/utils.tsx +++ b/src/component/Task/modals/ApplyDatabasePermission/CreateModal/utils.tsx @@ -1,30 +1,6 @@ -import { formatMessage } from '@/util/intl'; -import dayjs from 'dayjs'; import { permissionOptionsMap } from './const'; import HelpDoc from '@/component/helpDoc'; -const MAX_DATE = '9999-12-31 23:59:59'; -const MAX_DATE_LABEL = '9999-12-31'; - -export const getExpireTime = (expireTime, customExpireTime, isCustomExpireTime) => { - if (isCustomExpireTime) { - return customExpireTime?.valueOf(); - } else { - const [offset, unit] = expireTime.split(',') ?? []; - return offset === 'never' ? dayjs(MAX_DATE)?.valueOf() : dayjs().add(offset, unit)?.valueOf(); - } -}; - -export const getExpireTimeLabel = (expireTime) => { - const label = dayjs(expireTime).format('YYYY-MM-DD'); - return label === MAX_DATE_LABEL - ? formatMessage({ - id: 'src.component.Task.ApplyDatabasePermission.CreateModal.B5C7760D', - defaultMessage: '永不过期', - }) - : label; -}; - const Label: React.FC<{ text: string; docKey: string; diff --git a/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx b/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx index bcccfc137..bde697ad4 100644 --- a/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx +++ b/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx @@ -24,11 +24,13 @@ import { } from '@/d.ts'; import { getFormatDateTime } from '@/util/utils'; import { Descriptions, Divider, Alert, Space } from 'antd'; -import { getExpireTimeLabel, permissionOptionsMap } from '../'; +import { permissionOptionsMap } from '../'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import styles from './index.less'; import { DBType, IDatabase } from '@/d.ts/database'; import DatabaseIcon from '@/component/StatusIcon/DatabaseIcon'; import RiskLevelLabel from '@/component/RiskLevelLabel'; +import EllipsisText from '@/component/EllipsisText'; const getConnectionColumns = () => { return [ @@ -145,7 +147,7 @@ const TaskContent: React.FC = (props) => { }) /*"申请项目"*/ } > - {parameters?.project?.name} + ; @@ -65,7 +66,7 @@ const ApplyPermissionTaskContent: React.FC = (props) => { }) /* 申请项目 */ } > - {parameters?.project?.name || '-'} + { - if (isCustomExpireTime) { - return customExpireTime?.valueOf(); - } else { - const [offset, unit] = expireTime.split(',') ?? []; - return offset === 'never' ? dayjs(MAX_DATE)?.valueOf() : dayjs().add(offset, unit)?.valueOf(); - } -}; - -export const getExpireTimeLabel = (expireTime) => { - const label = dayjs(expireTime).format('YYYY-MM-DD'); - return label === MAX_DATE_LABEL - ? formatMessage({ - id: 'src.component.Task.ApplyTablePermission.CreateModal.BC4488C7', - defaultMessage: '永不过期', - }) - : label; -}; - const Label: React.FC<{ text: string; docKey: string; @@ -192,6 +171,7 @@ const CreateModal: React.FC = (props) => { const tableSelecterRef = useRef(null); const { run: getProjects, data: projects } = useRequest(listProjects, { defaultParams: [null, null, null], + manual: true, }); const projectOptions = projects?.contents?.map(({ name, id }) => ({ label: name, @@ -240,11 +220,16 @@ const CreateModal: React.FC = (props) => { .then(async (values) => { const { projectId, tables, types, expireTime, customExpireTime, applyReason } = values; const isCustomExpireTime = expireTime?.startsWith('custom'); + const allLoadedTables = tableSelecterRef.current + .getAllLoadedTables() + .map((table) => table.id); + const allLoadedTablesSet = new Set(allLoadedTables); + const filteredTables = tables.filter((table) => allLoadedTablesSet.has(table.tableId)); const parameters = { project: { id: projectId, }, - tables: groupTableByDataBase(tables), + tables: groupTableByDataBase(filteredTables), types, expireTime: getExpireTime(expireTime, customExpireTime, isCustomExpireTime), applyReason, @@ -298,12 +283,15 @@ const CreateModal: React.FC = (props) => { applyReason, }; // 默认获取要申请权限的库下面的表,并且展开 - // 目前一个工单只会关联一个库 - const databaseId = tables?.[0]?.databaseId; - if (projectId && databaseId) { + const databaseIds = [...new Set(tables?.map((table) => table.databaseId).filter(Boolean))]; + if (projectId && databaseIds.length) { await tableSelecterRef.current?.loadDatabases(); - await tableSelecterRef.current?.loadTables(databaseId); - tableSelecterRef.current?.expandTable(databaseId); + await Promise.all( + databaseIds.map(async (databaseId) => { + await tableSelecterRef.current?.loadTables(databaseId); + tableSelecterRef.current?.expandTable(databaseId); + }), + ); } form.setFieldsValue(formData); }, [applyTablePermissionData, form]); diff --git a/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx b/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx index 86741d510..60b7b9d30 100644 --- a/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx +++ b/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx @@ -22,8 +22,10 @@ import type { IApplyTablePermissionTaskParams, TaskDetail } from '@/d.ts'; import { getFormatDateTime } from '@/util/utils'; import { Descriptions, Divider, Space } from 'antd'; import { useMemo } from 'react'; -import { getExpireTimeLabel, permissionOptionsMap } from '../'; +import { permissionOptionsMap } from '../'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import RiskLevelLabel from '@/component/RiskLevelLabel'; +import EllipsisText from '@/component/EllipsisText'; const getConnectionColumns = () => { return [ @@ -138,7 +140,7 @@ const TaskContent: React.FC = (props) => { defaultMessage: '项目', })} > - {parameters?.project?.name} + = (props) => { }) /* 所属数据源 */ } > - {task?.database?.dataSource?.name || '-'} + , result: ITaskResult, hasFlow: boolean) { if (!task) { return []; @@ -216,7 +217,7 @@ export function getItems(task: TaskDetail, result: ITaskResult, id: 'odc.src.component.Task.DataMockerTask.DetailContent.DataSource', defaultMessage: '所属数据源', }), //'所属数据源' - task?.database?.dataSource?.name || '-', + , ], [ diff --git a/src/component/Task/modals/ExportTask/CreateModal/ExportForm/index.tsx b/src/component/Task/modals/ExportTask/CreateModal/ExportForm/index.tsx index db2886042..9a99736c3 100644 --- a/src/component/Task/modals/ExportTask/CreateModal/ExportForm/index.tsx +++ b/src/component/Task/modals/ExportTask/CreateModal/ExportForm/index.tsx @@ -64,14 +64,6 @@ const ExportForm: React.FC = inject('modalStore')( } }, [databaseId]); - useEffect(() => { - if (modalStore?.exportModalData?.databaseId) { - form.setFieldsValue({ - databaseId: modalStore?.exportModalData?.databaseId, - }); - } - }, [modalStore?.exportModalData?.databaseId]); - async function valid(callback: (haveError, values) => void) { try { const values = await form.validateFields(); @@ -174,6 +166,10 @@ const ExportForm: React.FC = inject('modalStore')( } } + useEffect(() => { + form.setFieldsValue(formData); + }, [formData]); + return ( = (props) => { useEffect(() => { initDefaultConfig(); - if (modalStore.exportModalData) { + if (modalStore.exportModalData?.name) { const newExportDbObjects = [ { objectName: modalStore.exportModalData.name, diff --git a/src/component/Task/modals/LogicDatabaseAsyncTask/DetailContent/index.tsx b/src/component/Task/modals/LogicDatabaseAsyncTask/DetailContent/index.tsx index 7030834eb..de077c165 100644 --- a/src/component/Task/modals/LogicDatabaseAsyncTask/DetailContent/index.tsx +++ b/src/component/Task/modals/LogicDatabaseAsyncTask/DetailContent/index.tsx @@ -7,6 +7,7 @@ import { InfoCircleOutlined } from '@ant-design/icons'; import { Descriptions, Divider, Space, Tooltip } from 'antd'; import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; import { getTaskExecStrategyMap } from '@/component/Task//const'; +import EllipsisText from '@/component/EllipsisText'; export const ErrorStrategy = { ABORT: formatMessage({ id: 'src.component.Task.LogicDatabaseAsyncTask.DetailContent.11ED2337', @@ -60,7 +61,7 @@ const LogicDatabaseAsyncTaskContent: React.FC = (props) => { defaultMessage: '数据库', })} > - {task?.database?.name || '-'} + = (props) => { defaultMessage: '所属项目', })} > - {task?.project?.name || '-'} + = defaultMessage: '所属项目', })} > - {task?.parameters?.databases?.[0]?.project?.name || '-'} + = (props) => { }) /*"所属数据源"*/ } > - {task?.database?.dataSource?.name || '-'} + {hasFlow && ( , result: ITaskResult, @@ -93,7 +94,7 @@ export const getItems = ( defaultMessage: '所属数据源', }) /* 所属数据源 */ } - content={task?.database?.dataSource?.name || '-'} + content={} /> = (props) => { defaultMessage: '所属数据源', })} > - {task?.database?.dataSource?.name || '-'} + , ], hasFlow ? riskItem : null, diff --git a/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx b/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx index 8de12e1bd..1d321a6df 100644 --- a/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx +++ b/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx @@ -58,6 +58,7 @@ import { TaskTypeMap } from '@/component/Task/component/TaskTable/const'; import { comparisonScopeMap, EOperationTypeMap } from '../CreateModal/interface'; import styles from './index.less'; import { getTaskExecStrategyMap } from '@/component/Task/const'; +import EllipsisText from '@/component/EllipsisText'; interface IStructureComparisonTaskContentProps { modalStore?: ModalStore; visible?: boolean; @@ -591,7 +592,7 @@ const StructureComparisonTaskContent: React.FC - {task?.project?.name || '-'} + - {task?.database?.dataSource?.name || '-'} + - {task?.database?.name || '-'} + - {task?.relatedDatabase?.dataSource?.name || '-'} + - {task?.relatedDatabase?.name || '-'} + ), + TaskShardingStrategy:

检索目标记录行的方式

, + TaskLmitData: (

{ diff --git a/src/d.ts/index.ts b/src/d.ts/index.ts index d76e0c840..cebd272e5 100644 --- a/src/d.ts/index.ts +++ b/src/d.ts/index.ts @@ -1852,7 +1852,7 @@ export interface IDatabaseSession { dbUser: string; executeTime: number; obproxyIp: string; - sessionId: number; + sessionId: string; sql: string; srcIp: string; svrIp: string; @@ -2622,6 +2622,7 @@ export enum MigrationInsertAction { export enum ShardingStrategy { FIXED_LENGTH = 'FIXED_LENGTH', MATCH = 'MATCH', + AUTO = 'AUTO', } export enum SyncTableStructureEnum { @@ -3270,6 +3271,7 @@ export enum TaskNodeStatus { FAILED = 'FAILED', WAIT_FOR_CONFIRM = 'WAIT_FOR_CONFIRM', PRE_CHECK_FAILED = 'PRE_CHECK_FAILED', + EXECUTING_ABNORMAL = 'EXECUTING_ABNORMAL', } export enum SQLContentType { diff --git a/src/page/Project/Database/components/ChangeOwnerModal/index.tsx b/src/page/Project/Database/components/ChangeOwnerModal/index.tsx index 028405db3..f64977ce4 100644 --- a/src/page/Project/Database/components/ChangeOwnerModal/index.tsx +++ b/src/page/Project/Database/components/ChangeOwnerModal/index.tsx @@ -17,11 +17,12 @@ import { listDatabases, updateDataBaseOwner } from '@/common/network/database'; import { IDatabase } from '@/d.ts/database'; import { formatMessage } from '@/util/intl'; import { useRequest } from 'ahooks'; -import { Form, message, Modal, Select } from 'antd'; +import { Form, message, Modal, Select, Space, Typography } from 'antd'; import { useCallback, useEffect, useState } from 'react'; import { DatabaseOwnerSelect } from '../DatabaseOwnerSelect'; import styles from './index.less'; import login from '@/store/login'; +import DatabaseIcon from '@/component/StatusIcon/DatabaseIcon'; interface IProps { visible: boolean; @@ -44,7 +45,8 @@ export default function ChangeOwnerModal({ const { run: startUpdateDataBase, loading: saveOwnerLoading } = useRequest(updateDataBaseOwner, { manual: true, }); - const [databaseOptions, setDatabaseOptions] = useState<{ label: string; value: number }[]>(); + const [databaseOptions, setDatabaseOptions] = + useState<{ label: React.ReactNode; value: number }[]>(); const [form] = Form.useForm<{ ownerIds: number[]; databaseList: number[] }>(); const loadData = async () => { @@ -60,8 +62,15 @@ export default function ChangeOwnerModal({ setDatabaseOptions( res?.contents?.map((i) => { return { - label: i.name, + label: ( + + + {i.name} + {i?.dataSource?.name} + + ), value: i.id, + name: i.name, }; }), ); @@ -153,6 +162,7 @@ export default function ChangeOwnerModal({ defaultMessage: '请选择数据库', })} options={databaseOptions} + optionLabelProp="name" /> ) : ( diff --git a/src/page/Project/Database/hooks/useData.ts b/src/page/Project/Database/hooks/useData.ts index 2df7795c7..3d35bccce 100644 --- a/src/page/Project/Database/hooks/useData.ts +++ b/src/page/Project/Database/hooks/useData.ts @@ -58,6 +58,7 @@ const useData = (id) => { name: searchValue?.value, environmentId: environmentId, includesPermittedAction: true, + includesDbOwner: true, type, connectType, dataSourceName: searchValue?.type === SearchType.DATASOURCE ? searchValue?.value : null, diff --git a/src/page/Project/User/ManageModal/Database/CreateAuth/index.tsx b/src/page/Project/User/ManageModal/Database/CreateAuth/index.tsx index df65fad24..1afdaa865 100644 --- a/src/page/Project/User/ManageModal/Database/CreateAuth/index.tsx +++ b/src/page/Project/User/ManageModal/Database/CreateAuth/index.tsx @@ -18,9 +18,9 @@ import { formatMessage } from '@/util/intl'; import { addDatabasePermissions } from '@/common/network/project'; import { expireTimeOptions, - getExpireTime, permissionOptions, } from '@/component/Task/modals/ApplyDatabasePermission/CreateModal'; +import { getExpireTime } from '@/component/Task/helper'; import DatabaseSelecter from '@/component/Task/component/DatabaseSelecter'; import { Button, Checkbox, DatePicker, Drawer, Form, message, Modal, Select, Space } from 'antd'; import React, { useState } from 'react'; diff --git a/src/page/Project/User/ManageModal/Database/TaskApplyList/index.tsx b/src/page/Project/User/ManageModal/Database/TaskApplyList/index.tsx index ea5ebc65e..f7cbce82b 100644 --- a/src/page/Project/User/ManageModal/Database/TaskApplyList/index.tsx +++ b/src/page/Project/User/ManageModal/Database/TaskApplyList/index.tsx @@ -24,8 +24,8 @@ import { ITableLoadOptions, } from '@/component/CommonTable/interface'; import SearchFilter from '@/component/SearchFilter'; -import { getExpireTimeLabel } from '@/component/Task/modals/ApplyDatabasePermission'; import TaskDetailModal from '@/component/Task/modals/DetailModals'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import type { IResponseData } from '@/d.ts'; import { TaskType } from '@/d.ts'; import { DatabasePermissionStatus, IDatabasePermission } from '@/d.ts/project'; diff --git a/src/page/Project/User/ManageModal/Database/UserAuthList/index.tsx b/src/page/Project/User/ManageModal/Database/UserAuthList/index.tsx index c90711f6b..dc5ac882c 100644 --- a/src/page/Project/User/ManageModal/Database/UserAuthList/index.tsx +++ b/src/page/Project/User/ManageModal/Database/UserAuthList/index.tsx @@ -23,7 +23,7 @@ import { ITableLoadOptions, } from '@/component/CommonTable/interface'; import SearchFilter from '@/component/SearchFilter'; -import { getExpireTimeLabel } from '@/component/Task/modals/ApplyDatabasePermission'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import type { IResponseData } from '@/d.ts'; import { DatabasePermissionStatus, IDatabasePermission } from '@/d.ts/project'; import { SearchOutlined } from '@ant-design/icons'; diff --git a/src/page/Project/User/ManageModal/Table/CreateAuth/index.tsx b/src/page/Project/User/ManageModal/Table/CreateAuth/index.tsx index 5533fd51c..ea56699ca 100644 --- a/src/page/Project/User/ManageModal/Table/CreateAuth/index.tsx +++ b/src/page/Project/User/ManageModal/Table/CreateAuth/index.tsx @@ -18,9 +18,10 @@ import { formatMessage } from '@/util/intl'; import { addTablePermissions } from '@/common/network/project'; import { expireTimeOptions, - getExpireTime, permissionOptions, } from '@/component/Task/modals/ApplyTablePermission/CreateModal'; +import { getExpireTime } from '@/component/Task/helper'; + import TableSelecter from '@/component/Task/component/TableSelecter'; import { groupTableIdsByDataBase } from '@/component/Task/component/TableSelecter/util'; import { Button, Checkbox, DatePicker, Drawer, Form, Modal, Select, Space, message } from 'antd'; diff --git a/src/page/Project/User/ManageModal/Table/TaskApplyList/index.tsx b/src/page/Project/User/ManageModal/Table/TaskApplyList/index.tsx index e073d0596..aea439149 100644 --- a/src/page/Project/User/ManageModal/Table/TaskApplyList/index.tsx +++ b/src/page/Project/User/ManageModal/Table/TaskApplyList/index.tsx @@ -23,8 +23,8 @@ import { ITableLoadOptions, } from '@/component/CommonTable/interface'; import SearchFilter from '@/component/SearchFilter'; -import { getExpireTimeLabel } from '@/component/Task/modals/ApplyDatabasePermission'; import TaskDetailModal from '@/component/Task/modals/DetailModals'; +import { getExpireTimeLabel } from '@/component/Task/helper'; import type { IResponseData } from '@/d.ts'; import { TaskType } from '@/d.ts'; import { ITablePermission, TablePermissionStatus } from '@/d.ts/project'; diff --git a/src/page/Project/User/ManageModal/Table/UserAuthList/index.tsx b/src/page/Project/User/ManageModal/Table/UserAuthList/index.tsx index 780813145..d6e3c0493 100644 --- a/src/page/Project/User/ManageModal/Table/UserAuthList/index.tsx +++ b/src/page/Project/User/ManageModal/Table/UserAuthList/index.tsx @@ -23,7 +23,6 @@ import { ITableLoadOptions, } from '@/component/CommonTable/interface'; import SearchFilter from '@/component/SearchFilter'; -import { getExpireTimeLabel } from '@/component/Task/modals/ApplyTablePermission'; import type { IResponseData } from '@/d.ts'; import { ITablePermission, TablePermissionStatus } from '@/d.ts/project'; import { SearchOutlined } from '@ant-design/icons'; @@ -35,6 +34,7 @@ import { tablePermissionTypeMap, } from '../'; import StatusLabel from '../Status'; +import { getExpireTimeLabel } from '@/component/Task/helper'; const getColumns = (params: { paramOptions: ITableLoadOptions; diff --git a/src/page/Secure/RiskLevel/components/InnerRiskLevel.tsx b/src/page/Secure/RiskLevel/components/InnerRiskLevel.tsx index 1ec9f60e5..362c3540f 100644 --- a/src/page/Secure/RiskLevel/components/InnerRiskLevel.tsx +++ b/src/page/Secure/RiskLevel/components/InnerRiskLevel.tsx @@ -376,25 +376,31 @@ const InnerRiskLevel: React.FC = ({ currentRiskLevel, memor {(inFields, { add: inAdd, remove: inRemove }) => { return (

- {inFields?.map((inField, inIndex) => ( - - ))} + {inFields?.map((inField, inIndex) => { + const key = `${inIndex}_${ + formRef?.getFieldsValue()?.conditions?.[index] + ?.children?.[inIndex]?.expression + }`; + return ( + + ); + })}
), }, - { - key: 'host', - label: '主机 IP/域名:', - children: {value?.host}, - }, - { - key: 'host', - label: '端口', - children: {value?.port}, - }, + ...(haveOCP() + ? [ + { + key: 'instanceId', + label: formatMessage({ + id: 'odc.component.ConnectionPopover.InstanceIdTenantId', + defaultMessage: '实例ID/租户ID:', + }), + children: `${value?.instanceId}/${value?.tenantId}`, + }, + ] + : [ + { + key: 'host', + label: '主机 IP/域名:', + children: {value?.host}, + }, + { + key: 'port', + label: '端口', + children: {value?.port}, + }, + ]), { key: 'username', label: formatMessage({ diff --git a/src/plugins/defaultConfig.ts b/src/plugins/defaultConfig.ts index 0998f614d..5db4f12c2 100644 --- a/src/plugins/defaultConfig.ts +++ b/src/plugins/defaultConfig.ts @@ -81,9 +81,9 @@ export default { canDownloadNewVersion: true, task: { sys: true, - isSupportTaksImport: true, - isSupportTaksExport: true, - isSupportTaksTerminate: true, + isSupportTaksImport: false, + isSupportTaksExport: false, + isSupportTaksTerminate: false, }, systemConfig: { default: null, From 62ebd0b605aa050530054f07537a60dc7c5409ff Mon Sep 17 00:00:00 2001 From: xiaokang Date: Tue, 12 Aug 2025 15:41:50 +0800 Subject: [PATCH 010/239] chore: oic extract --- .../Task/component/ExecuteFailTip/index.tsx | 9 ++- .../ImportModal/DatabaseChangeItem.tsx | 10 ++- .../ImportModal/DatabaseInfoPopover.tsx | 11 ++- .../ImportModal/ImportPreviewTable.tsx | 34 +++++++-- .../Task/component/ImportModal/index.tsx | 21 +++++- .../Task/component/ImportModal/useColumn.tsx | 73 +++++++++++++++---- .../Task/component/ImportModal/useImport.tsx | 62 ++++++++++++++-- .../Task/component/TaskTable/index.tsx | 10 ++- src/d.ts/importTask.ts | 11 +-- src/locales/must/strings/zh-CN.json | 38 +++++++++- .../components/RecentlyDatabase/index.tsx | 12 ++- .../SessionSelect/SessionDropdown/index.tsx | 9 ++- 12 files changed, 254 insertions(+), 46 deletions(-) diff --git a/src/component/Task/component/ExecuteFailTip/index.tsx b/src/component/Task/component/ExecuteFailTip/index.tsx index c95e2c4fa..806bc7b51 100644 --- a/src/component/Task/component/ExecuteFailTip/index.tsx +++ b/src/component/Task/component/ExecuteFailTip/index.tsx @@ -1,11 +1,14 @@ +import { formatMessage } from '@/util/intl'; import { Alert } from 'antd'; export default function ExecuteFailTip() { return ( {value?.host}, }, { key: 'port', - label: '端口', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.FF8162DA', + defaultMessage: '端口', + }), children: {value?.port}, }, ]), + { key: 'username', label: formatMessage({ diff --git a/src/component/Task/component/ImportModal/ImportPreviewTable.tsx b/src/component/Task/component/ImportModal/ImportPreviewTable.tsx index a5938d915..10ed18c72 100644 --- a/src/component/Task/component/ImportModal/ImportPreviewTable.tsx +++ b/src/component/Task/component/ImportModal/ImportPreviewTable.tsx @@ -23,6 +23,7 @@ interface ImportPreviewTableProps { string, { databaseId: number | null; targetDatabaseId: number | null } >; + setDatabaseSelections: React.Dispatch< React.SetStateAction< Record @@ -242,21 +243,39 @@ const ImportPreviewTable: React.FC = ({ const map = { TO_BE_IMPORTED: ( - 勾选需要导入的工单,导入后将重新启用。导入前请检查涉及的新旧数据库对象是否一致,否则导入或执行时可能出现失败。 + {formatMessage({ + id: 'src.component.Task.component.ImportModal.C264F2F5', + defaultMessage: + '勾选需要导入的工单,导入后将重新启用。导入前请检查涉及的新旧数据库对象是否一致,否则导入或执行时可能出现失败。', + })} + handleShowOnlyImportableChange(e.target.checked)} > - 仅显示已选择数据库的工单 + {formatMessage({ + id: 'src.component.Task.component.ImportModal.3137AA28', + defaultMessage: '仅显示已选择数据库的工单', + })} ), + [ScheduleNonImportableType.IMPORTED]: ( -
以下工单已导入,无需重复操作。
+
+ {formatMessage({ + id: 'src.component.Task.component.ImportModal.F8B503DB', + defaultMessage: '以下工单已导入,无需重复操作。', + })} +
), + [ScheduleNonImportableType.TYPE_NOT_MATCH]: (
- 以下工单类型不匹配、无法导入,建议选择对应工单类型重新导入。 + {formatMessage({ + id: 'src.component.Task.component.ImportModal.49FDDB00', + defaultMessage: '以下工单类型不匹配、无法导入,建议选择对应工单类型重新导入。', + })}
), }; @@ -272,12 +291,17 @@ const ImportPreviewTable: React.FC = ({ > {groupedData['TO_BE_IMPORTED']?.length > 0 && ( - 待导入 + {formatMessage({ + id: 'src.component.Task.component.ImportModal.F42820DF', + defaultMessage: '待导入', + })} + {groupedData['TO_BE_IMPORTED']?.length || 0} )} + {Object.keys(ScheduleNonImportableType) ?.filter( (key) => diff --git a/src/component/Task/component/ImportModal/index.tsx b/src/component/Task/component/ImportModal/index.tsx index 271ab6514..bf621a50f 100644 --- a/src/component/Task/component/ImportModal/index.tsx +++ b/src/component/Task/component/ImportModal/index.tsx @@ -330,7 +330,10 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy }} className={notConfirmButSubmit ? styles.checkboxError : null} > - 我己确认导入的工单新旧数据库对象一致 + {formatMessage({ + id: 'src.component.Task.component.ImportModal.EA6397CD', + defaultMessage: '我己确认导入的工单新旧数据库对象一致', + })} @@ -392,8 +401,12 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy showIcon message={ <> - 仅支持导入由 阿里云 OceanBase 数据研发 或 ODC - 导出的配置文件;在导入之前,请先将添加相关数据源、 井指定对应的项目。 + {formatMessage({ + id: 'src.component.Task.component.ImportModal.A28A2A00', + defaultMessage: + '仅支持导入由 阿里云 OceanBase 数据研发 或 ODC\n 导出的配置文件;在导入之前,请先将添加相关数据源、 井指定对应的项目。', + })} + {}}> @@ -548,6 +552,7 @@ const SessionDropdown: React.FC = (props) => { })} )} + {checkedKeys?.length === canCheckedDbKeys?.length && (
+ } + rootClassName={styles.detailDrawer} + > +
+ { + props.onDetailTypeChange(e.target.value); + }} + > + + 基本信息 + + + 执行记录 + + + 操作记录 + + + +
+ + {enabledAction && ( +
+ +
+ )} + + ); +}; + +export default CommonTaskDetailModal; diff --git a/src/component/Schedule/components/ScheduleExecuteRecord/index.less b/src/component/Schedule/components/ScheduleExecuteRecord/index.less new file mode 100644 index 000000000..d28e232cc --- /dev/null +++ b/src/component/Schedule/components/ScheduleExecuteRecord/index.less @@ -0,0 +1,34 @@ +.scheduleExecuteRecord { + :global { + .ant-table-tbody > tr.ant-table-row-selected > td { + background: var(--background-primary-color); + } + + .ant-table-row, + .ant-table-thead { + background: var(--background-primary-color); + + .ant-table-cell:first-child { + border-left: 1px solid var(--odc-border-color); + } + + .ant-table-cell { + border-right: 1px solid var(--odc-border-color); + border-bottom: 1px solid var(--odc-border-color); + } + } + + .ant-table-thead { + .ant-table-cell { + border-top: 1px solid var(--odc-border-color); + .anticon { + color: var(--icon-color-normal); + } + .active .anticon, + .anticon.active { + color: var(--icon-color-focus); + } + } + } + } +} diff --git a/src/component/Schedule/components/ScheduleExecuteRecord/index.tsx b/src/component/Schedule/components/ScheduleExecuteRecord/index.tsx new file mode 100644 index 000000000..7e3091a16 --- /dev/null +++ b/src/component/Schedule/components/ScheduleExecuteRecord/index.tsx @@ -0,0 +1,187 @@ +import { + ScheduleType, + SchedulePageType, + IScheduleRecord, + ScheduleRecordParameters, + ScheduleStatus, +} from '@/d.ts/schedule'; +import React, { useEffect, useState } from 'react'; +import { listChangeLog } from '@/common/network/schedule'; +import { formatMessage } from '@/util/intl'; +import { TaskOperationType, TaskRecord, TaskRecordParameters, Operation, TaskType } from '@/d.ts'; +import { getFormatDateTime } from '@/util/utils'; +import StatusItem from '@/component/Task/component/TaskDetailModal/status'; +import Action from '@/component/Action'; +import DisplayTable from '@/component/DisplayTable'; +import styles from './index.less'; +import ScheduleExecuteRecordDetail from '../ScheduleExecuteRecordDetail'; +import { inject, observer } from 'mobx-react'; +import { ScheduleStore } from '@/store/schedule'; +import { useLoop } from '@/util/hooks/useLoop'; + +export const operationTypeMap = { + [TaskOperationType.CREATE]: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.CreateATask', + defaultMessage: '创建任务', + }), //创建任务 + [TaskOperationType.UPDATE]: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.EditTask', + defaultMessage: '编辑任务', + }), //编辑任务 + [TaskOperationType.PAUSE]: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.DisableATask', + defaultMessage: '停用任务', + }), //停用任务 + [TaskOperationType.TERMINATE]: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.TerminateATask', + defaultMessage: '终止任务', + }), //终止任务 + [TaskOperationType.RESUME]: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.EnableTasks', + defaultMessage: '启用任务', + }), //启用任务 + [TaskOperationType.DELETE]: '删除任务', +}; + +interface ScheduleExecuteRecordProps { + schedule: IScheduleRecord; + scheduleStore?: ScheduleStore; +} + +const ScheduleExecuteRecord: React.FC = ({ + schedule, + scheduleStore, +}) => { + const [opRecord, setOpRecord] = useState(null); + const [detailVisible, setDetailVisible] = useState(false); + const [operation, setOperation] = useState(); + + useEffect(() => { + if (schedule?.scheduleId) { + loadData(); + } + }, [schedule?.scheduleId]); + + useEffect(() => { + return () => { + destory?.(); + }; + }, []); + + const { loop: loadData, destory } = useLoop(() => { + return async () => { + if (schedule?.scheduleId) { + const res = await listChangeLog(schedule?.scheduleId); + if (scheduleStore?.openOperationId) { + const opRecord = res?.contents.find((item) => item.id === scheduleStore?.openOperationId); + scheduleStore.setOpenOperationId(null); + if (opRecord) { + handleDetailVisible(opRecord, true); + } + } + setOpRecord(res?.contents?.sort((a, b) => b.createTime - a.createTime)); + } + }; + }, 6500); + + const handleDetailVisible = (operation: Operation, visible: boolean = false) => { + setOperation(operation as Operation); + + setDetailVisible(visible); + }; + + const getConnectionColumns = (params: { + onOpenDetail: (operation: Operation, visible: boolean) => void; + scheduleType: ScheduleType; + }) => { + return [ + { + dataIndex: 'id', + title: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.EventOperations', + defaultMessage: '事件操作', + }), //事件操作 + ellipsis: true, + width: 140, + render: (id, record) => { + return {operationTypeMap?.[record.type]}; + }, + }, + + { + dataIndex: 'createTime', + title: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.OperationTime', + defaultMessage: '操作时间', + }), //操作时间 + ellipsis: true, + sorter: (a, b) => a.createTime - b.createTime, + width: 180, + render: (createTime) => getFormatDateTime(createTime), + }, + + { + dataIndex: 'status', + title: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.ApprovalStatus', + defaultMessage: '审批状态', + }), //审批状态 + ellipsis: true, + width: 140, + render: (status, record) => { + return ; + }, + }, + + { + dataIndex: 'action', + title: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.Operation', + defaultMessage: '操作', + }), //操作 + ellipsis: true, + width: 92, + render: (_, record) => { + return ( + <> + { + params?.onOpenDetail(record, true); + }} + > + 查看 + + + ); + }, + }, + ]; + }; + + return ( + <> + + setDetailVisible(false)} + operation={operation} + onReload={() => { + loadData(); + }} + /> + + ); +}; + +export default inject('scheduleStore')(observer(ScheduleExecuteRecord)); diff --git a/src/component/Schedule/components/ScheduleExecuteRecordDetail/ApprovalRecord.tsx b/src/component/Schedule/components/ScheduleExecuteRecordDetail/ApprovalRecord.tsx new file mode 100644 index 000000000..538bb6eec --- /dev/null +++ b/src/component/Schedule/components/ScheduleExecuteRecordDetail/ApprovalRecord.tsx @@ -0,0 +1,53 @@ +import { getTaskDetail } from '@/common/network/task'; +import { TaskOperationType } from '@/d.ts'; +import { formatMessage } from '@/util/intl'; +import { Space, Spin } from 'antd'; +import React, { useEffect, useState } from 'react'; +import TaskFlow from '@/component/Task/component/TaskDetailModal/TaskFlow'; +import { operationTypeMap } from '../ScheduleExecuteRecord'; +import { useRequest } from 'ahooks'; +import { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; + +interface IProps { + id: number; + operationType: TaskOperationType; +} +const FlowModal: React.FC = function (props) { + const { id, operationType } = props; + const [task, setTask] = useState(null); + + const { loading, run } = useRequest(getTaskDetail, { + manual: true, + }); + + const getTask = async function (id) { + const data = await run(id); + setTask(data); + }; + useEffect(() => { + if (id) { + getTask(id); + } + }, [id]); + return ( + <> +
+ + { + formatMessage({ + id: 'odc.component.CommonTaskDetailModal.FlowModal.ActionEvents', + defaultMessage: '操作事件:', + }) /*操作事件:*/ + } + + {operationTypeMap?.[operationType]} +
+
+ 风险等级: + +
+ {task && } + + ); +}; +export default FlowModal; diff --git a/src/component/Schedule/components/ScheduleExecuteRecordDetail/ChangeDetail.tsx b/src/component/Schedule/components/ScheduleExecuteRecordDetail/ChangeDetail.tsx new file mode 100644 index 000000000..681ecd095 --- /dev/null +++ b/src/component/Schedule/components/ScheduleExecuteRecordDetail/ChangeDetail.tsx @@ -0,0 +1,75 @@ +import { formatMessage } from '@/util/intl'; +import React, { useEffect, useState } from 'react'; +import { Modal, Descriptions, Spin } from 'antd'; +import { getOperationDetail } from '@/common/network/schedule'; +import { Operation } from '@/d.ts'; +import DiffEditor from '@/component/MonacoEditor/DiffEditor'; +import styles from './index.less'; +import { useRequest } from 'ahooks'; + +interface ChangeDetailProps { + scheduleId: number; + scheduleChangeLogId: number; +} + +const ChangeDetail: React.FC = (props) => { + const { scheduleId, scheduleChangeLogId } = props; + const [data, setData] = useState(); + + useEffect(() => { + if (scheduleId && scheduleChangeLogId) { + loadData(); + } + }, [scheduleId, scheduleChangeLogId]); + + const { run: fetchOperationDetail, loading } = useRequest(getOperationDetail, { + manual: true, + }); + + const loadData = async () => { + setData(null); + const data = await fetchOperationDetail(scheduleId, scheduleChangeLogId); + setData(data); + }; + + return ( +
+ + + {formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.AF00DE0E', + defaultMessage: '变更前:', + })} + + + {formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.1CF173E7', + defaultMessage: '变更后:', + })} + + + + {data && ( +
+ {loading ? ( + + ) : ( + + )} +
+ )} +
+ ); +}; + +export default ChangeDetail; diff --git a/src/component/Schedule/components/ScheduleExecuteRecordDetail/index.less b/src/component/Schedule/components/ScheduleExecuteRecordDetail/index.less new file mode 100644 index 000000000..f34d4350d --- /dev/null +++ b/src/component/Schedule/components/ScheduleExecuteRecordDetail/index.less @@ -0,0 +1,20 @@ +.changeDetail { + height: calc(100% - 28px); + display: flex; + flex-direction: column; + .diffEditor { + position: relative; + flex: 1; + display: flex; + justify-content: center; + align-items: center; + :global { + .monaco-editor { + .margin, + .monaco-editor-background { + background: var(--background-tertraiy-color); + } + } + } + } +} diff --git a/src/component/Schedule/components/ScheduleExecuteRecordDetail/index.tsx b/src/component/Schedule/components/ScheduleExecuteRecordDetail/index.tsx new file mode 100644 index 000000000..4c965618e --- /dev/null +++ b/src/component/Schedule/components/ScheduleExecuteRecordDetail/index.tsx @@ -0,0 +1,113 @@ +import { Operation } from '@/d.ts'; +import { Drawer, Space, Radio, Button } from 'antd'; +import { useState } from 'react'; +import { formatMessage } from '@/util/intl'; +import ChangeDetail from './ChangeDetail'; +import ApprovalRecord from './ApprovalRecord'; +import { IScheduleRecord, ScheduleRecordParameters } from '@/d.ts/schedule'; +import ApprovalModal from '@/component/Task/component/ApprovalModal'; +import { useLoop } from '@/util/hooks/useLoop'; + +enum ExecuteRecordDetailType { + DETAIL = 'DETAIL', + CHANGE_DETAIL = 'CHANGE_DETAIL', +} + +interface ScheduleExecuteRecordDetailProps { + visible: boolean; + onClose: () => void; + operation: Operation; + schedule: IScheduleRecord; + onReload: () => void; +} + +const ScheduleExecuteRecordDetail: React.FC = (props) => { + const { visible, onClose, operation, schedule, onReload } = props; + const [detailType, setDetailType] = useState(ExecuteRecordDetailType.DETAIL); + + const [approvalVisible, setApprovalVisible] = useState(false); + const [approvalStatus, setApprovalStatus] = useState(false); + const handleApprovalVisible = (approvalStatus: boolean = false, visible: boolean = false) => { + setApprovalVisible(visible); + setApprovalStatus(approvalStatus); + }; + + return ( + + + + + ) + } + > +
+ setDetailType(e.target.value)}> + + {formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.3D4F5474', + defaultMessage: '审批记录', + })} + + + {formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.5C706BA6', + defaultMessage: '变更详情', + })} + + +
+ + {detailType === ExecuteRecordDetailType.DETAIL && ( + + )} + {detailType === ExecuteRecordDetailType.CHANGE_DETAIL && ( + + )} + { + onClose?.(); + onReload?.(); + }} + onCancel={() => { + handleApprovalVisible(false); + }} + /> +
+ ); +}; + +export default ScheduleExecuteRecordDetail; diff --git a/src/component/Schedule/components/ScheduleMiniFlowSpan/index.tsx b/src/component/Schedule/components/ScheduleMiniFlowSpan/index.tsx new file mode 100644 index 000000000..656263700 --- /dev/null +++ b/src/component/Schedule/components/ScheduleMiniFlowSpan/index.tsx @@ -0,0 +1,81 @@ +import React, { useEffect, useState } from 'react'; +import { TaskDetail, TaskRecordParameters } from '@/d.ts'; +import { Button, Popover, Spin } from 'antd'; +import MiniTaskFlow from '../MiniFlow'; +import { getTaskDetail } from '@/common/network/task'; +import { useRequest } from 'ahooks'; +import { IScheduleRecord, ScheduleRecordParameters } from '@/d.ts/schedule'; + +interface IProps { + onDetail: () => void; + record: IScheduleRecord; +} +const ScheduleMiniFlowSpan: React.FC = ({ onDetail, record }) => { + const { candidateApprovers, approveInstanceId } = record; + const candidateApproversName = candidateApprovers?.map((item) => item.name)?.join(', '); + const [popoverOpen, setPopoverOpen] = useState(false); + + return ( + + + + } + > +
+ 审批中 + {candidateApproversName && `(${candidateApproversName})`} +
+
+ ); +}; + +export default ScheduleMiniFlowSpan; + +const Content = ({ + Id, + onDetail, + visible, +}: { + Id: number; + onDetail: () => void; + visible: boolean; +}) => { + const [task, setTask] = useState>(); + const { run: fetchTaskDetail, loading } = useRequest(getTaskDetail, { + manual: true, + }); + + useEffect(() => { + if (visible && Id) { + initData(); + } + }, [visible, Id]); + + const initData = async () => { + const res = await fetchTaskDetail(Id); + setTask(res); + }; + + return ( + + {task && } + + + ); +}; diff --git a/src/component/Schedule/components/ScheduleResult/index.tsx b/src/component/Schedule/components/ScheduleResult/index.tsx new file mode 100644 index 000000000..48c1b96f4 --- /dev/null +++ b/src/component/Schedule/components/ScheduleResult/index.tsx @@ -0,0 +1,27 @@ +import React, { useEffect, useState, useMemo } from 'react'; +import ExcecuteDetail from '@/component/Schedule/components/ExcecuteDetail'; +import SqlplanExcecuteDetail from '@/component/Schedule/components/ExcecuteDetail/SqlplanExcecuteDetail'; +import { scheduleTask, SubTaskType } from '@/d.ts/scheduleTask'; + +interface ScheduleResultProps { + subTask: scheduleTask; +} + +const ScheduleResult: React.FC = (props) => { + const { subTask } = props; + + if ( + [ + SubTaskType.DATA_ARCHIVE, + SubTaskType.DATA_DELETE, + SubTaskType.DATA_ARCHIVE_ROLLBACK, + SubTaskType.DATA_ARCHIVE_DELETE, + ].includes(subTask.type) + ) { + return ; + } + + return ; +}; + +export default ScheduleResult; diff --git a/src/component/Schedule/components/ScheduleStatusLabel/index.tsx b/src/component/Schedule/components/ScheduleStatusLabel/index.tsx new file mode 100644 index 000000000..826da082e --- /dev/null +++ b/src/component/Schedule/components/ScheduleStatusLabel/index.tsx @@ -0,0 +1,110 @@ +import { ScheduleStatus, ScheduleType, ScheduleActionsEnum } from '@/d.ts/schedule'; +import { ScheduleStatusTextMap } from '@/constant/schedule'; +import { + CheckCircleFilled, + CloseCircleFilled, + ExclamationCircleFilled, + LoadingOutlined, + StopFilled, + PauseCircleOutlined, +} from '@ant-design/icons'; +import { Space } from 'antd'; + +const ScheduleStatusInfo = { + [ScheduleStatus.CREATING]: { + icon: ( + + ), + }, + [ScheduleStatus.PAUSE]: { + icon: ( + + ), + }, + [ScheduleStatus.ENABLED]: { + icon: ( + + ), + }, + [ScheduleStatus.TERMINATED]: { + icon: ( + + ), + }, + [ScheduleStatus.CANCELED]: { + icon: ( + + ), + }, + [ScheduleStatus.COMPLETED]: { + icon: ( + + ), + }, + [ScheduleStatus.EXECUTION_FAILED]: { + icon: ( + + ), + }, +}; + +interface IProps { + status: ScheduleStatus; +} +const ScheduleStatusLabel: React.FC = ({ status }) => { + const statusObj = ScheduleStatusInfo[status]; + return ( + + {statusObj ? ( + <> + {statusObj?.icon} + + {ScheduleStatusTextMap[status]} + + + ) : null} + + ); +}; + +export default ScheduleStatusLabel; diff --git a/src/component/Schedule/components/ScheduleTable/DatabaseColumn.tsx b/src/component/Schedule/components/ScheduleTable/DatabaseColumn.tsx new file mode 100644 index 000000000..bd5f70140 --- /dev/null +++ b/src/component/Schedule/components/ScheduleTable/DatabaseColumn.tsx @@ -0,0 +1,51 @@ +import { IScheduleRecord, ScheduleRecordParameters } from '@/d.ts/schedule'; +import Icon from '@ant-design/icons'; +import { ReactComponent as TargetDatabaseSvg } from '@/svgr/targetDatabase.svg'; +import { ReactComponent as SourceDatabaseSvg } from '@/svgr/sourceDatabase.svg'; +import { ReactComponent as DatabaseSvg } from '@/svgr/database2.svg'; +import { Tooltip } from 'antd'; +import styles from './index.less'; + +interface IProps { + record: IScheduleRecord; +} +const DatabaseColumn: React.FC = (props) => { + const { record } = props; + return ( +
+ {record?.attributes?.databaseInfo && ( + +
+ + {record?.attributes?.databaseInfo?.name ?? '-'} +
+
+ )} + {record?.attributes?.sourceDataBaseInfo && ( + +
+ + {record?.attributes?.sourceDataBaseInfo?.name ?? '-'} +
+
+ )} + {record?.attributes?.targetDataBaseInfo && ( + +
+ + {record?.attributes?.targetDataBaseInfo?.name ?? '-'} +
+
+ )} +
+ ); +}; + +export default DatabaseColumn; diff --git a/src/component/Schedule/components/ScheduleTable/ScheduleNameColumns.tsx b/src/component/Schedule/components/ScheduleTable/ScheduleNameColumns.tsx new file mode 100644 index 000000000..5b257bb13 --- /dev/null +++ b/src/component/Schedule/components/ScheduleTable/ScheduleNameColumns.tsx @@ -0,0 +1,76 @@ +import { IScheduleRecord, ScheduleDetailType, ScheduleRecordParameters } from '@/d.ts/schedule'; +import Icon from '@ant-design/icons'; +import classNames from 'classnames'; +import { Tooltip } from 'antd'; +import { ReactComponent as UserSvg } from '@/svgr/user.svg'; +import dayjs from 'dayjs'; +import styles from './index.less'; + +interface IProps { + record: IScheduleRecord; + delList: number[]; + onDetailVisible: ( + schedule: IScheduleRecord, + visible: boolean, + detailType?: ScheduleDetailType, + ) => void; +} +const ScheduleName: React.FC = (props) => { + const { record, delList, onDetailVisible } = props; + + return ( +
+
+ + { + if (delList?.includes(record?.scheduleId)) return; + onDetailVisible(record, true); + }} + > + {record?.scheduleName ?? '-'} + + +
+
+ { + onDetailVisible(record, true); + }} + > + #{record?.scheduleId} + + · + +
创建人:{record?.creator?.name}
+
账号:{record?.creator?.accountName}
+ + } + placement="bottom" + > +
+ + {record?.creator?.name} +
+
+ 创建于 {dayjs(record?.createTime).format('YYYY-MM-DD HH:mm:ss')}· +
+ + {record?.project?.name} + +
+
+
+ ); +}; + +export default ScheduleName; diff --git a/src/component/Schedule/components/ScheduleTable/index.less b/src/component/Schedule/components/ScheduleTable/index.less new file mode 100644 index 000000000..0df5150ec --- /dev/null +++ b/src/component/Schedule/components/ScheduleTable/index.less @@ -0,0 +1,129 @@ +.menuItem { + padding: 4px 15px; + cursor: pointer; + &:hover { + background-color: var(--hover-color); + } +} + +.newSchedulePopover { + :global { + .ant-popover-content { + .ant-popover-inner { + padding: 0px !important; + .ant-popover-inner-content { + .ant-space { + padding: 4px !important; + } + } + } + } + } +} + +.scheduleTable { + :global { + .ant-spin-nested-loading { + height: 100%; + .ant-spin-container { + .ant-table-wrapper { + height: 100%; + } + } + } + .ant-input-search { + width: 178px; + } + .ant-select-single:not(.ant-select-customize-input) .ant-select-selector { + padding: 0 11px 0 0; + } + .ant-table-tbody > tr > td > .ant-table-wrapper:only-child .ant-table { + margin-top: 0; + } + .ant-pagination-next[aria-disabled='false'] { + .anticon { + color: var(--icon-color-normal); + } + } + .ant-pagination-disabled { + .anticon { + color: var(--icon-color-disable); + } + } + } +} +.hoverLink:hover { + color: #1890ff !important; + cursor: pointer; +} + +.tip { + color: var(--text-color-secondary); +} + +.batchOperationDropdown { + :global { + .ant-dropdown-menu { + .ant-dropdown-menu-item { + padding: 0; + .ant-dropdown-menu-title-content { + .ant-btn { + width: 100%; + justify-content: left; + } + } + } + } + } +} + +.scheduleNameColumn { + color: var(--text-color-secondary); + padding: 10px 0px 10px 6px; + .columns { + display: flex; + gap: 8px; + } + .scheduleName { + font-weight: 600; + color: black; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .creator { + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + } + .project { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + } +} + +.dbColumnsEllipsis { + width: fit-content; + max-width: 100%; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} +.mr4 { + margin-right: 4px; +} + +.scheduleNameTooltip { + max-width: 500px !important; +} + +.ellipsis { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} diff --git a/src/component/Schedule/components/ScheduleTable/index.tsx b/src/component/Schedule/components/ScheduleTable/index.tsx new file mode 100644 index 000000000..28a4ad4b3 --- /dev/null +++ b/src/component/Schedule/components/ScheduleTable/index.tsx @@ -0,0 +1,707 @@ +import { inject, observer } from 'mobx-react'; +import TableCard from '@/component/Table/TableCard'; +import { Button, Tooltip, Popover, Space, Typography, Dropdown } from 'antd'; +import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { DownOutlined } from '@ant-design/icons'; +import { ScheduleStore } from '@/store/schedule'; +import type { ITableInstance } from '@/component/CommonTable/interface'; +import type { PageStore } from '@/store/page'; +import Header from '@/component/Schedule/layout/Header'; +import ParamsContext from '@/component/Schedule/context/ParamsContext'; +import { IScheduleParam, ISubTaskParam, SchedulePageMode } from '@/component/Schedule/interface'; +import { listProjects } from '@/common/network/project'; +import { useRequest } from 'ahooks'; +import { SchedulePageTextMap } from '@/constant/schedule'; +import styles from './index.less'; +import CommonTable from '@/component/CommonTable'; +import { CommonTableMode } from '@/component/CommonTable/interface'; +import { formatMessage } from '@/util/intl'; +import ScheduleStatusLabel from '@/component/Schedule/components/ScheduleStatusLabel'; +import { scheduleThatCanBeExport } from '@/constant/triangularization'; +import { AsyncTaskOperationButton } from '@/component/Task/component/AsyncTaskOperationButton'; +import login from '@/store/login'; +import { useImport } from '@/component/Task/component/ImportModal/useImport'; +import ImportModal from '@/component/Task/component/ImportModal'; +import { + getExportConfig, + getTerminateConfig, + isScheduleMigrateTask, +} from '@/component/Task/component/AsyncTaskOperationButton/helper'; +import { useLoop } from '@/util/hooks/useLoop'; +import ScheduleActions from '../Actions/ScheduleActions'; +import ScheduleNameColumns from './ScheduleNameColumns'; +import { Perspective } from '@/component/Schedule/interface'; +import { + ScheduleType, + SchedulePageType, + IScheduleRecord, + ScheduleRecordParameters, + ScheduleDetailType, + ScheduleStatus, +} from '@/d.ts/schedule'; +import classNames from 'classnames'; +import { getFormatDateTime } from '@/util/utils'; +import ScheduleTaskStatusLabel from '../ScheduleTaskStatusLabel'; +import { IResponseData } from '@/d.ts'; +import ScheduleTaskActions from '@/component/Schedule/components/Actions/ScheduleTaskActions'; +import { SubTypeTextMap } from '@/constant/scheduleTask'; +import { scheduleTask } from '@/d.ts/scheduleTask'; +import odc from '@/plugins/odc'; +import ProjectContext from '@/page/Project/ProjectContext'; +import { isProjectArchived } from '@/page/Project/helper'; +import { IPagination } from '@/component/Schedule/interface'; +import ScheduleMiniFlowSpan from '../ScheduleMiniFlowSpan'; +import DatabaseColumn from './DatabaseColumn'; +import { useScheduleSelection } from '@/component/Schedule/hooks/useScheduleSelection'; + +export const SCHEDULE_EXECUTE_TIME_KEY = 'schedule:executeTime'; +export const SCHEDULE_EXECUTE_DATE_KEY = 'schedule:executeDate'; +export const SUB_TASK_EXECUTE_TIME_KEY = 'subTask:executeTime'; +export const SUB_TASK_EXECUTE_DATE_KEY = 'subTask:executeDate'; + +interface IProps { + tableRef: React.RefObject; + scheduleStore?: ScheduleStore; + pageStore?: PageStore; + scheduleRes: IResponseData>; + scheduleTaskRes: IResponseData; + scheduleTabType: SchedulePageType; + getTaskList: ( + args: IScheduleParam | ISubTaskParam, + perspective: Perspective, + pagination: IPagination, + ) => Promise; + onDetailVisible: ( + schedule: IScheduleRecord, + visible: boolean, + detailType?: ScheduleDetailType, + ) => void; + onSubTaskDetailVisible: (subTask: scheduleTask, visible: boolean) => void; + onMenuClick?: (type: ScheduleType) => void; + onReloadList: () => void; + loading: boolean; + setLoading: React.Dispatch>; + onApprovalVisible?: (status: boolean, id: number) => void; + mode?: SchedulePageMode; + defaultScheduleStatus?: ScheduleStatus; + params: IScheduleParam; + subTaskParams: ISubTaskParam; + setParams: React.Dispatch>; + setsubTaskParams: React.Dispatch>; + setPerspective: React.Dispatch>; + perspective: Perspective; + pagination: IPagination; + setPagination: React.Dispatch>; +} + +const ScheduleTable: React.FC = (props) => { + const { + scheduleStore, + pageStore, + getTaskList, + scheduleTabType, + tableRef, + scheduleRes, + scheduleTaskRes, + onMenuClick, + onDetailVisible, + onSubTaskDetailVisible, + setLoading, + loading, + onApprovalVisible, + defaultScheduleStatus, + mode, + params, + subTaskParams, + setParams, + setsubTaskParams, + setPerspective, + perspective, + pagination, + setPagination, + } = props; + const [hoverInNewScheduleMenuBtn, setHoverInNewScheduleMenuBtn] = useState(false); + const [hoverInNewScheduleMenu, setHoverInNewScheduleMenu] = useState(false); + const isAll = SchedulePageType.ALL === scheduleTabType; + const [importProjectId, setImportProjectId] = useState(); + const { isSubmitImport, debounceSubmit } = useImport(props.onReloadList, importProjectId); + const [importModalVisible, setImportModalVisible] = useState(false); + const { activePageKey } = pageStore; + const [delList, setDelList] = useState([]); + const { project } = useContext(ProjectContext) || {}; + const projectArchived = isProjectArchived(project); + /** 是否是作业视角 */ + const isScheduleView = useMemo(() => { + return perspective === Perspective.scheduleView; + }, [perspective]); + + const { selectedRow, rowSelection, clearSelection } = useScheduleSelection({ + scheduleStore: scheduleStore, + scheduleTabType: scheduleTabType, + ScheduleRes: scheduleRes, + tableRef, + }); + + useEffect(() => { + if (defaultScheduleStatus) { + setParams({ + ...params, + status: [defaultScheduleStatus as ScheduleStatus], + }); + } + }, [defaultScheduleStatus]); + + useEffect(() => { + if (isScheduleView && scheduleRes) { + setPagination({ + current: scheduleRes?.page?.number, + pageSize: scheduleRes?.page?.size ? scheduleRes.page.size : pagination?.pageSize, + }); + } + if (!isScheduleView && scheduleTaskRes) { + setPagination({ + current: scheduleTaskRes?.page?.number, + pageSize: scheduleTaskRes?.page?.size ? scheduleTaskRes.page.size : pagination?.pageSize, + }); + } + }, [scheduleTaskRes, scheduleRes, isScheduleView]); + + const { data: resProjects } = useRequest(listProjects, { + defaultParams: [null, 1, 400], + }); + + const { loop: loadData, destory } = useLoop((count) => { + return async ( + args: IScheduleParam | ISubTaskParam, + perspective: Perspective, + propsPagination: IPagination, + ) => { + if (mode === SchedulePageMode.MULTI_PAGE && activePageKey !== scheduleTabType) { + destory(); + return; + } + if (propsPagination?.pageSize) { + setPagination(propsPagination); + } + await getTaskList(args, perspective, propsPagination); + setLoading(false); + }; + }, 6000); + + useEffect(() => { + return () => { + destory?.(); + }; + }, []); + + const handleChangeParams = useCallback( + (params: IScheduleParam | ISubTaskParam, perspective: Perspective, pagination: IPagination) => { + setLoading(true); + loadData(params, perspective, pagination); + }, + [], + ); + + useEffect(() => { + if (isScheduleView) { + params.timeRange && + localStorage.setItem(SCHEDULE_EXECUTE_TIME_KEY, JSON.stringify(params.timeRange)); + params.executeDate && + localStorage.setItem(SCHEDULE_EXECUTE_DATE_KEY, JSON.stringify(params.executeDate)); + handleChangeParams(params, perspective, { ...pagination, current: 1 }); + } else { + subTaskParams.timeRange && + localStorage.setItem(SUB_TASK_EXECUTE_TIME_KEY, JSON.stringify(subTaskParams.timeRange)); + subTaskParams.executeDate && + localStorage.setItem(SUB_TASK_EXECUTE_DATE_KEY, JSON.stringify(subTaskParams.executeDate)); + handleChangeParams(subTaskParams, perspective, { ...pagination, current: 1 }); + } + }, [params, scheduleTabType, subTaskParams]); + + const newSchedule = () => { + return ( + setHoverInNewScheduleMenuBtn(true)} + onMouseLeave={() => setHoverInNewScheduleMenuBtn(false)} + > + {Object.keys(ScheduleType).map((item) => { + return ( +
{ + setHoverInNewScheduleMenuBtn(false); + onMenuClick(item as ScheduleType); + }} + > + {SchedulePageTextMap[item]} +
+ ); + })} +
+ ); + }; + + const columns = [ + { + title: '作业', + dataIndex: 'scheduleId', + width: 500, + render: (id, record) => { + return ( + } + delList={delList} + onDetailVisible={onDetailVisible} + /> + ); + }, + }, + { + title: '数据库', + dataIndex: 'database', + width: 120, + render: (type, record) => { + return } />; + }, + }, + ...(scheduleTabType === SchedulePageType.ALL + ? [ + { + title: '类型', + dataIndex: 'type', + width: 100, + render: (type, record) => { + return <>{SchedulePageTextMap[type] || type || '-'}; + }, + }, + ] + : []), + { + title: formatMessage({ + id: 'odc.component.TaskTable.Status', + defaultMessage: '状态', + }), + dataIndex: 'status', + width: 150, + render: (status, record) => { + return ( +
+
+ +
+ {record?.approvable && record?.approveInstanceId && ( + { + onDetailVisible(record, true, ScheduleDetailType.OPERATION_RECORD); + scheduleStore.setOpenOperationId(record?.latestChangedLogId); + }} + /> + )} +
+ ); + }, + }, + { + title: '操作', + dataIndex: 'actions', + width: 140, + render: (_, record) => { + return ( + + ); + }, + }, + ]; + + const subTaskColumns = [ + { + dataIndex: 'id', + title: '执行记录ID', + ellipsis: true, + width: 80, + render: (id, record) => { + return ( +
{ + onSubTaskDetailVisible?.(record, true); + }} + > + #{id} +
+ ); + }, + }, + { + dataIndex: 'scheduleName', + title: '所属作业', + ellipsis: true, + width: 220, + render: (scheduleName, record) => { + return ( +
+ +
所属作业:{scheduleName}
+
作业ID:{record?.scheduleId}
+ + } + > +
+
{scheduleName}
+
#{record?.scheduleId}
+
+
+
+ ); + }, + }, + { + dataIndex: 'project', + title: '项目', + ellipsis: true, + width: 200, + render: (project) => project?.name, + }, + { + dataIndex: 'type', + title: '类型', + ellipsis: true, + width: 120, + render: (type) => SubTypeTextMap[type], + }, + { + dataIndex: 'createTime', + title: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskRecord.CreationTime', + defaultMessage: '创建时间', + }), //创建时间 + ellipsis: true, + width: 180, + render: (createTime) => getFormatDateTime(createTime), + }, + { + dataIndex: 'status', + title: '状态', + ellipsis: true, + width: 140, + render: (status, record) => { + return ; + }, + }, + { + dataIndex: 'action', + title: formatMessage({ + id: 'odc.component.CommonTaskDetailModal.TaskRecord.Operation', + defaultMessage: '操作', + }), //操作 + ellipsis: true, + width: 140, + render: (_, record) => { + return ( + onSubTaskDetailVisible?.(record, true)} + /> + ); + }, + }, + ]; + + const newALLScheduleOperation = useMemo(() => { + if (!isAll || projectArchived) return; + + return ( + + + + ); + }, [isAll, hoverInNewScheduleMenuBtn, hoverInNewScheduleMenu]); + + const newScheduleOperation = useMemo(() => { + if (isAll || projectArchived) return; + const isSupportTaksImport = odc?.appConfig?.task?.isSupportTaksImport; + let scheduleTypeLabel = SchedulePageTextMap[scheduleTabType]; + const menuItems = []; + if ( + scheduleThatCanBeExport.includes(scheduleTabType) && + !login.isPrivateSpace() && + isSupportTaksImport + ) { + menuItems.push({ + key: 'import', + disabled: isSubmitImport, + label: ( + + {formatMessage( + { + id: 'src.component.Task.component.TaskTable.D4FAED98', + defaultMessage: '导入{activeTaskLabel}', + }, + { activeTaskLabel: scheduleTypeLabel }, + )} + + ), + }); + } + + return ( + { + switch (val?.key) { + case 'import': { + setImportModalVisible(true); + break; + } + default: { + } + } + }, + }} + > +
+ + ); + }, [isAll, scheduleTabType, isSubmitImport]); + + const batchOperation = () => { + if (projectArchived || isAll) return; + const isSupportTaksExport = odc?.appConfig?.task?.isSupportTaksExport; + const isSupportTaksTerminate = odc?.appConfig?.task?.isSupportTaksTerminate; + let menuItems = []; + if (isSupportTaksTerminate) { + menuItems.push({ + key: 'batchTerminate', + label: ( + { + clearSelection(); + props.onReloadList?.(); + }} + {...getTerminateConfig(selectedRow, true)} + buttonType="text" + dataSource={selectedRow as any[]} + /> + ), + }); + } + if (isSupportTaksExport && isScheduleMigrateTask(scheduleTabType as unknown as ScheduleType)) { + menuItems.push({ + key: 'batchExport', + label: ( + { + clearSelection(); + props.onReloadList?.(); + }} + {...getExportConfig(selectedRow)} + buttonType="text" + dataSource={selectedRow as any[]} + /> + ), + }); + } + if (!menuItems?.length) { + return; + } + return ( + + + + ); + }; + + const handleOnLoad = useCallback(async (e, isScheduleView, params, subTaskParams) => { + setLoading(true); + if (isScheduleView) { + loadData(params, Perspective.scheduleView, { + current: 1, + pageSize: e.pageSize ? e.pageSize : pagination?.pageSize, + }); + } else { + loadData(subTaskParams, Perspective.executionView, { + current: 1, + pageSize: e.pageSize ? e.pageSize : pagination?.pageSize, + }); + } + }, []); + + return ( + + {newALLScheduleOperation} + {newScheduleOperation} + {batchOperation()} + + } + extra={ + { + setLoading(true); + if (isScheduleView) { + loadData(params, perspective, pagination); + } else { + loadData(subTaskParams, perspective, pagination); + } + }, + }} + > + +
+ + + } + > + handleOnLoad(e, isScheduleView, params, subTaskParams)} + onChange={(e) => { + if (e.pagination) { + if (isScheduleView) { + loadData(params, Perspective.scheduleView, { + current: e?.pagination?.current, + pageSize: e?.pagination?.pageSize ? e.pagination.pageSize : pagination?.pageSize, + }); + } else { + loadData(subTaskParams, Perspective.executionView, { + current: e?.pagination?.current, + pageSize: e?.pagination?.pageSize ? e.pagination.pageSize : pagination?.pageSize, + }); + } + } + }} + enabledReload={false} + tableProps={{ + className: styles.scheduleTable, + columns: isScheduleView ? columns : subTaskColumns, + loading, + rowKey: isScheduleView ? 'scheduleId' : 'id', + dataSource: isScheduleView ? scheduleRes?.contents : scheduleTaskRes?.contents, + pagination: { + current: isScheduleView ? scheduleRes?.page?.number : scheduleTaskRes?.page?.number, + total: isScheduleView + ? scheduleRes?.page?.totalElements + : scheduleTaskRes?.page?.totalElements, + }, + }} + showSelectedInfoBar={false} + rowSelecter={ + ![SchedulePageType.ALL]?.includes(scheduleTabType) && isScheduleView ? rowSelection : null + } + /> + setImportModalVisible(false)} + onOk={(scheduleTaskImportRequest, previewData, projectId) => { + setImportModalVisible(false); + setImportProjectId(projectId); + debounceSubmit(scheduleTaskImportRequest, previewData); + }} + /> + + ); +}; + +export default inject('scheduleStore', 'pageStore')(observer(ScheduleTable)); diff --git a/src/component/Schedule/components/ScheduleTaskStatusLabel/index.tsx b/src/component/Schedule/components/ScheduleTaskStatusLabel/index.tsx new file mode 100644 index 000000000..d0bc1fa24 --- /dev/null +++ b/src/component/Schedule/components/ScheduleTaskStatusLabel/index.tsx @@ -0,0 +1,146 @@ +import { Space } from 'antd'; +import { ScheduleTaskStatus } from '@/d.ts/scheduleTask'; +import { + CheckCircleFilled, + CloseCircleFilled, + ExclamationCircleFilled, + LoadingOutlined, + StopFilled, +} from '@ant-design/icons'; +import { ScheduleTaskStatusTextMap } from '@/constant/scheduleTask'; + +interface IProps { + status: ScheduleTaskStatus; +} + +const ScheduleTaskStatusInfo = { + [ScheduleTaskStatus.PREPARING]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.RUNNING]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.ABNORMAL]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.PAUSING]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.PAUSED]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.RESUMING]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.CANCELING]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.FAILED]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.EXEC_TIMEOUT]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.CANCELED]: { + icon: ( + + ), + }, + [ScheduleTaskStatus.DONE]: { + icon: ( + + ), + }, +}; + +const ScheduleTaskStatusLabel: React.FC = ({ status }) => { + const statusObj = ScheduleTaskStatusInfo[status]; + return ( + + {statusObj ? ( + <> + {statusObj?.icon} + + {ScheduleTaskStatusTextMap[status]} + + + ) : null} + + ); +}; + +export default ScheduleTaskStatusLabel; diff --git a/src/component/Schedule/components/SubTaskDetailModal/index.tsx b/src/component/Schedule/components/SubTaskDetailModal/index.tsx new file mode 100644 index 000000000..e9e208667 --- /dev/null +++ b/src/component/Schedule/components/SubTaskDetailModal/index.tsx @@ -0,0 +1,166 @@ +import { scheduleTask, ScheduleTaskDetailType } from '@/d.ts/scheduleTask'; +import { Drawer, message, Radio, Spin } from 'antd'; +import styles from '../ScheduleDetailModal/index.less'; +import ScheduleTaskActions from '../Actions/ScheduleTaskActions'; +import ScheduleTaskStatusLabel from '../ScheduleTaskStatusLabel'; +import type { ILog } from '@/component/Task/component/Log'; +import { ITaskResult, CommonTaskLogType, Operation } from '@/d.ts'; +import ScheduleResult from '../ScheduleResult'; +import TaskLog from '@/component/Task/component/Log'; + +interface TaskContentProps { + taskContent?: React.ReactNode; + isLoading: boolean; + detailType: ScheduleTaskDetailType; + subTask: scheduleTask; + log: ILog; + result: ITaskResult; + logType: CommonTaskLogType; + handleLogTypeChange: (type: CommonTaskLogType) => void; + opRecord: Operation[]; +} +const TaskContent: React.FC = (props) => { + const { + isLoading, + taskContent, + detailType, + subTask, + log, + result, + logType, + handleLogTypeChange, + opRecord, + } = props; + let content = null; + + switch (detailType) { + case ScheduleTaskDetailType.INFO: + content = taskContent; + break; + case ScheduleTaskDetailType.EXECUTE_RESULT: + content = ; + break; + // case ScheduleTaskDetailType.OPERATION_RECORD: + // content = {}} />; + // break; + case ScheduleTaskDetailType.LOG: + content = ( + + ); + break; + } + + return ( +
+ {content} +
+ ); +}; + +interface ICommonSubTaskDetailModalProps { + width?: number; + isSplit?: boolean; + theme?: string; + downloadUrl?: string; + visible: boolean; + onClose: () => void; + taskContent?: React.ReactNode; + isLoading: boolean; + detailType: ScheduleTaskDetailType; + onDetailTypeChange: (type: ScheduleTaskDetailType) => void; + enabledAction: boolean; + onReloadList?: () => void; + subTask: scheduleTask; + log: ILog; + result: ITaskResult; + logType: CommonTaskLogType; + opRecord: Operation[]; + handleLogTypeChange: (type: CommonTaskLogType) => void; +} + +const SubTaskDetailModal: React.FC = (props) => { + const { + visible, + width = 1100, + onClose, + onDetailTypeChange, + detailType, + taskContent, + subTask, + log, + result, + logType, + handleLogTypeChange, + opRecord, + } = props; + + return ( + + {`#${subTask?.id} 执行详情`} + + } + > +
+ { + props.onDetailTypeChange(e.target.value); + }} + > + + 基本信息 + + + 执行结果 + + {/* + 操作记录 + */} + + 任务日志 + + + +
+ +
+ {}} + /> +
+
+ ); +}; + +export default SubTaskDetailModal; diff --git a/src/component/Schedule/const.tsx b/src/component/Schedule/const.tsx new file mode 100644 index 000000000..4d6336d9d --- /dev/null +++ b/src/component/Schedule/const.tsx @@ -0,0 +1,95 @@ +import { IOperationTypeRole, ScheduleStatus, ScheduleActionsEnum } from '@/d.ts/schedule'; +import { ScheduleTaskActionsEnum, ScheduleTaskStatus } from '@/d.ts/scheduleTask'; + +/** 作业状态对应的操作 */ +const ScheduleStatus2Actions = { + [ScheduleStatus.ENABLED]: [ + ScheduleActionsEnum.STOP, + ScheduleActionsEnum.DISABLE, + ScheduleActionsEnum.VIEW, + ScheduleActionsEnum.CLONE, + ScheduleActionsEnum.SHARE, + ], + [ScheduleStatus.PAUSE]: [ + ScheduleActionsEnum.EDIT, + ScheduleActionsEnum.ENABLE, + ScheduleActionsEnum.STOP, + ScheduleActionsEnum.VIEW, + ScheduleActionsEnum.CLONE, + ScheduleActionsEnum.SHARE, + ], + [ScheduleStatus.CANCELED]: [ + ScheduleActionsEnum.VIEW, + ScheduleActionsEnum.CLONE, + ScheduleActionsEnum.SHARE, + ], + [ScheduleStatus.COMPLETED]: [ + ScheduleActionsEnum.DELETE, + ScheduleActionsEnum.VIEW, + ScheduleActionsEnum.CLONE, + ScheduleActionsEnum.SHARE, + ], + [ScheduleStatus.TERMINATED]: [ + ScheduleActionsEnum.DELETE, + ScheduleActionsEnum.VIEW, + ScheduleActionsEnum.CLONE, + ScheduleActionsEnum.SHARE, + ], + [ScheduleStatus.CREATING]: [ + ScheduleActionsEnum.VIEW, + ScheduleActionsEnum.CLONE, + ScheduleActionsEnum.SHARE, + ], + /** 删除不可见 */ + [ScheduleStatus.DELETED]: [], + + [ScheduleStatus.EXECUTION_FAILED]: [], +}; + +/** 作业类任务状态对应的操作 */ +const ScheduleTaskStatus2Actions = { + [ScheduleTaskStatus.PREPARING]: [ + ScheduleTaskActionsEnum.VIEW, + ScheduleTaskActionsEnum.SHARE, + ScheduleTaskActionsEnum.STOP, + ], + [ScheduleTaskStatus.RUNNING]: [ + ScheduleTaskActionsEnum.VIEW, + ScheduleTaskActionsEnum.SHARE, + ScheduleTaskActionsEnum.PAUSE, + ScheduleTaskActionsEnum.STOP, + ], + [ScheduleTaskStatus.ABNORMAL]: [ + ScheduleTaskActionsEnum.VIEW, + ScheduleTaskActionsEnum.SHARE, + ScheduleTaskActionsEnum.RETRY, + ScheduleTaskActionsEnum.STOP, + ], + [ScheduleTaskStatus.PAUSING]: [ + ScheduleTaskActionsEnum.VIEW, + ScheduleTaskActionsEnum.SHARE, + ScheduleTaskActionsEnum.STOP, + ], + [ScheduleTaskStatus.PAUSED]: [ + ScheduleTaskActionsEnum.VIEW, + ScheduleTaskActionsEnum.SHARE, + ScheduleTaskActionsEnum.RESTORE, + ScheduleTaskActionsEnum.STOP, + ], + [ScheduleTaskStatus.RESUMING]: [ + ScheduleTaskActionsEnum.VIEW, + ScheduleTaskActionsEnum.SHARE, + ScheduleTaskActionsEnum.STOP, + ], + [ScheduleTaskStatus.CANCELING]: [ + ScheduleTaskActionsEnum.VIEW, + ScheduleTaskActionsEnum.SHARE, + ScheduleTaskActionsEnum.STOP, + ], + [ScheduleTaskStatus.FAILED]: [ScheduleTaskActionsEnum.VIEW, ScheduleTaskActionsEnum.SHARE], + [ScheduleTaskStatus.EXEC_TIMEOUT]: [ScheduleTaskActionsEnum.VIEW, ScheduleTaskActionsEnum.SHARE], + [ScheduleTaskStatus.CANCELED]: [ScheduleTaskActionsEnum.VIEW, ScheduleTaskActionsEnum.SHARE], + [ScheduleTaskStatus.DONE]: [ScheduleTaskActionsEnum.VIEW, ScheduleTaskActionsEnum.SHARE], +}; + +export { ScheduleStatus2Actions, ScheduleTaskStatus2Actions }; diff --git a/src/component/Schedule/context/ParamsContext.tsx b/src/component/Schedule/context/ParamsContext.tsx new file mode 100644 index 000000000..b3760b242 --- /dev/null +++ b/src/component/Schedule/context/ParamsContext.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { IProject } from '@/d.ts/project'; +import { + IScheduleParam, + Perspective, + ISubTaskParam, + SchedulePageMode, +} from '@/component/Schedule/interface'; +import { SchedulePageType } from '@/d.ts/schedule'; + +interface IParamsContext { + params?: IScheduleParam; + setParams?: ( + patch: Partial | ((prevState: IScheduleParam) => Partial), + ) => void; + subTaskParams?: ISubTaskParam; + setsubTaskParams?: ( + patch: Partial | ((prevState: ISubTaskParam) => Partial), + ) => void; + projectList: IProject[]; + scheduleTabType?: SchedulePageType; + perspective?: Perspective; + setPerspective?: React.Dispatch>; + isScheduleView?: boolean; + reload?: () => void; + mode?: SchedulePageMode; + setLoading?: React.Dispatch>; +} + +const ParamsContext: React.Context = React.createContext({ + projectList: [], +}); + +export default ParamsContext; diff --git a/src/component/Schedule/context/ScheduleDetailContext.ts b/src/component/Schedule/context/ScheduleDetailContext.ts new file mode 100644 index 000000000..1ee426d48 --- /dev/null +++ b/src/component/Schedule/context/ScheduleDetailContext.ts @@ -0,0 +1,9 @@ +import { IState } from '../interface'; +import React from 'react'; + +interface IScheduleDetailContext { + handleDetailVisible: (task: any, visible?: boolean) => void; + setState: (patch: Partial | ((prevState: IState) => Partial)) => void; +} + +export const ScheduleDetailContext = React.createContext(null); diff --git a/src/component/Schedule/context/createScheduleContext.ts b/src/component/Schedule/context/createScheduleContext.ts new file mode 100644 index 000000000..63e12d0a7 --- /dev/null +++ b/src/component/Schedule/context/createScheduleContext.ts @@ -0,0 +1,9 @@ +import React from 'react'; +import { IDatabase } from '@/d.ts/database'; + +interface ICreateScheduleContext { + createScheduleDatabase: IDatabase; + setCreateScheduleDatabase: React.Dispatch>; +} + +export const CreateScheduleContext = React.createContext(null); diff --git a/src/component/Schedule/helper.ts b/src/component/Schedule/helper.ts new file mode 100644 index 000000000..ecbfa6140 --- /dev/null +++ b/src/component/Schedule/helper.ts @@ -0,0 +1,136 @@ +import { IOperationTypeRole, ScheduleActionsEnum, ScheduleType } from '@/d.ts/schedule'; +import { schedlueConfig } from '@/page/Schedule/const'; +import { + IScheduleParam, + ISubTaskParam, + SchedulePageMode, + ScheduleTab, + ScheduleTaskTab, +} from './interface'; +import { history } from '@umijs/max'; +import { SchedulePageTextMap } from '@/constant/schedule'; +import { openCreateSchedulePage } from '@/store/helper/page/openPage'; +import { TaskExecStrategy } from '@/d.ts'; +import { formatMessage } from '@/util/intl'; +import { IPageType } from '@/d.ts/_index'; +import { + SCHEDULE_EXECUTE_TIME_KEY, + SCHEDULE_EXECUTE_DATE_KEY, + SUB_TASK_EXECUTE_TIME_KEY, + SUB_TASK_EXECUTE_DATE_KEY, +} from './components/ScheduleTable'; +import dayjs from 'dayjs'; + +export const getFirstEnabledSchedule = () => { + return Object.values(schedlueConfig).find((item) => item.enabled()); +}; + +export const gotoCreateSchedulePage = ( + type: ScheduleType, + mode?: SchedulePageMode, + isEdit: boolean = false, + projectId?: number, +) => { + switch (mode) { + case SchedulePageMode.PROJECT: { + isEdit && + history.push( + `/project/${projectId}/${IPageType.Project_Schedule}/create?type=${type}&&isEdit=true`, + ); + !isEdit && + history.push(`/project/${projectId}/${IPageType.Project_Schedule}/create?type=${type}`); + break; + } + case SchedulePageMode.MULTI_PAGE: { + isEdit && openCreateSchedulePage(type, `编辑${SchedulePageTextMap[type]}`); + !isEdit && openCreateSchedulePage(type); + break; + } + default: { + isEdit && history.push(`/schedule/create?type=${type}&&isEdit=true`); + !isEdit && history.push(`/schedule/create?type=${type}`); + } + } +}; + +export const getScheduleExecStrategyMap = (type: ScheduleType) => { + switch (type) { + case ScheduleType.DATA_ARCHIVE: + case ScheduleType.DATA_DELETE: + case ScheduleType.SQL_PLAN: + case ScheduleType.PARTITION_PLAN: + return { + [TaskExecStrategy.TIMER]: formatMessage({ + id: 'odc.src.component.Task.CycleExecution', + defaultMessage: '周期执行', + }), //'周期执行' + [TaskExecStrategy.CRON]: formatMessage({ + id: 'odc.src.component.Task.CycleExecution.1', + defaultMessage: '周期执行', + }), //'周期执行' + [TaskExecStrategy.DAY]: formatMessage({ + id: 'odc.src.component.Task.CycleExecution.2', + defaultMessage: '周期执行', + }), //'周期执行' + [TaskExecStrategy.MONTH]: formatMessage({ + id: 'odc.src.component.Task.CycleExecution.3', + defaultMessage: '周期执行', + }), //'周期执行' + [TaskExecStrategy.WEEK]: formatMessage({ + id: 'odc.src.component.Task.CycleExecution.4', + defaultMessage: '周期执行', + }), //'周期执行' + [TaskExecStrategy.START_NOW]: formatMessage({ + id: 'odc.src.component.Task.ExecuteImmediately', + defaultMessage: '立即执行', + }), //'立即执行' + [TaskExecStrategy.START_AT]: formatMessage({ + id: 'odc.src.component.Task.TimedExecution', + defaultMessage: '定时执行', + }), //'定时执行' + }; + } +}; + +export const getDefaultParam: () => IScheduleParam = () => { + const _defaultParam: IScheduleParam = { + searchValue: undefined, + searchType: undefined, + type: undefined, + status: [], + projectIds: [], + sort: '', + tab: ScheduleTab.all, + approveStatus: [], + timeRange: 7, + executeDate: [null, null], + }; + _defaultParam.timeRange = + JSON.parse(localStorage.getItem(SCHEDULE_EXECUTE_TIME_KEY)) ?? _defaultParam.timeRange; + const [start, end] = JSON.parse(localStorage?.getItem(SCHEDULE_EXECUTE_DATE_KEY)) ?? [null, null]; + if (!!start && !!end) { + _defaultParam.executeDate = [dayjs(start), dayjs(end)]; + } + return _defaultParam; +}; + +export const getDefaultSubTaskParam: () => ISubTaskParam = () => { + const _defaultParam: ISubTaskParam = { + searchValue: undefined, + searchType: undefined, + type: undefined, + status: [], + projectIds: [], + sort: '', + tab: ScheduleTaskTab.all, + timeRange: 'ALL', + executeDate: [undefined, undefined], + }; + _defaultParam.timeRange = + JSON.parse(localStorage.getItem(SUB_TASK_EXECUTE_TIME_KEY)) ?? _defaultParam.timeRange; + const [start, end] = JSON.parse(localStorage?.getItem(SUB_TASK_EXECUTE_DATE_KEY)) ?? [null, null]; + if (!!start && !!end) { + _defaultParam.executeDate = [dayjs(start), dayjs(end)]; + } + return _defaultParam; +}; diff --git a/src/component/Schedule/hooks/useScheduleSelection.tsx b/src/component/Schedule/hooks/useScheduleSelection.tsx new file mode 100644 index 000000000..253885daa --- /dev/null +++ b/src/component/Schedule/hooks/useScheduleSelection.tsx @@ -0,0 +1,115 @@ +import { + IScheduleRecord, + SchedulePageType, + ScheduleRecordParameters, + ScheduleStatus, +} from '@/d.ts/schedule'; +import { ScheduleStore } from '@/store/schedule'; +import type { IResponseData } from '@/d.ts'; +import type { ITableInstance } from '@/component/CommonTable/interface'; +import { useEffect, useState, useMemo } from 'react'; +import odc from '@/plugins/odc'; +import { + scheduleStatusThatCanBeExport, + scheduleThatCanBeExport, + SchedulestatusThatCanBeTerminate, +} from '@/constant/triangularization'; + +const isSupportTaksExport = odc?.appConfig?.task?.isSupportTaksExport; +const isSupportTaksImport = odc?.appConfig?.task?.isSupportTaksImport; +const isSupportTaksTerminate = odc?.appConfig?.task?.isSupportTaksTerminate; + +interface UseTaskSelectionProps { + scheduleStore: ScheduleStore; + scheduleTabType: SchedulePageType; + ScheduleRes: IResponseData>; + tableRef: React.RefObject; +} + +export const useScheduleSelection = ({ + scheduleStore, + scheduleTabType, + ScheduleRes, + tableRef, +}: UseTaskSelectionProps) => { + const [selectedRow, setSelectedRow] = useState[]>(); + + // 当任务列表数据变化时,清理无效的selectedRowKeys + useEffect(() => { + if (ScheduleRes?.contents?.length > 0 && scheduleStore.selectedRowKeys.length > 0) { + const rules = scheduleThatCanBeExport.includes(scheduleTabType) + ? [...scheduleStatusThatCanBeExport, ...SchedulestatusThatCanBeTerminate] + : [...SchedulestatusThatCanBeTerminate]; + + const validSelectedRowKeys = scheduleStore.selectedRowKeys.filter((keyId) => { + const taskInCurrentList = ScheduleRes.contents.find( + (schedule) => schedule.scheduleId === keyId, + ); + return taskInCurrentList && rules.includes(taskInCurrentList.status); + }); + + // 更新store + if (validSelectedRowKeys.length !== scheduleStore.selectedRowKeys.length) { + scheduleStore.setSelectedRowKeys(validSelectedRowKeys); + setSelectedRow(selectedRow?.filter((row) => validSelectedRowKeys.includes(row.scheduleId))); + tableRef.current?.setSelectedRowKeys(validSelectedRowKeys); + } + } + }, [ScheduleRes?.contents]); + + useEffect(() => { + if (ScheduleRes?.contents?.length > 0) { + const selectedRows = ScheduleRes.contents.filter((row) => + scheduleStore.selectedRowKeys.includes(row.scheduleId), + ) as IScheduleRecord[]; + setSelectedRow(selectedRows); + } else { + setSelectedRow([]); + } + }, [scheduleStore.selectedRowKeys, ScheduleRes?.contents]); + + useEffect(() => { + if (scheduleStore.selectedRowKeys.length === 0) { + tableRef.current?.resetSelectedRows?.(); + } + }, [scheduleStore.selectedRowKeys]); + + const rowSelection = useMemo(() => { + const rules = scheduleThatCanBeExport.includes(scheduleTabType) + ? [...scheduleStatusThatCanBeExport, ...SchedulestatusThatCanBeTerminate] + : [...SchedulestatusThatCanBeTerminate]; + + return { + selectedRowKeys: scheduleStore.selectedRowKeys, + options: [], + onChange: ( + selectedRowKeys: React.Key[], + selectedRows: IScheduleRecord[], + ) => { + scheduleStore.setSelectedRowKeys(selectedRowKeys); + setSelectedRow(selectedRows); + }, + getCheckboxProps: (record: IScheduleRecord) => { + return { + disabled: !rules?.includes(record.status), + name: record.scheduleId?.toString(), + }; + }, + }; + }, [scheduleStore.selectedRowKeys, scheduleTabType]); + + const clearSelection = () => { + setSelectedRow([]); + scheduleStore.setSelectedRowKeys([]); + }; + + return { + selectedRow, + setSelectedRow, + rowSelection: + isSupportTaksExport || isSupportTaksImport || isSupportTaksTerminate + ? rowSelection + : undefined, + clearSelection, + }; +}; diff --git a/src/component/Schedule/index.less b/src/component/Schedule/index.less new file mode 100644 index 000000000..23d347ae8 --- /dev/null +++ b/src/component/Schedule/index.less @@ -0,0 +1,61 @@ +.task { + display: flex; + height: 100%; + overflow: hidden; + .sider { + flex-grow: 0; + flex-shrink: 0; + height: 100%; + } +} + +.schedlueSider { + width: 148px; + height: 100%; + padding: 0 12px 12px 0; + overflow: auto; + overflow-x: hidden; + border-right: 1px solid var(--divider-color); +} + +.groupName { + padding-top: 6px; + color: var(--text-color-hint); + line-height: 28px; +} +.groupItem { + display: flex; + align-items: center; + justify-content: space-between; + padding: 2px 12px; + color: var(--text-color-primary); + line-height: 28px; + cursor: pointer; + &:hover { + background-color: var(--hover-color); + } + &.selected { + :global { + .ant-typography { + line-height: 28px; + color: var(--text-color-link); + } + } + background-color: var(--focus-color); + border-radius: 2px; + } + :global { + .ant-typography { + line-height: 28px; + } + } +} + +.content { + flex-grow: 1; + flex-shrink: 1; + height: 100%; + padding-bottom: 12px; + padding-left: 12px; + overflow: auto; +} diff --git a/src/component/Schedule/index.tsx b/src/component/Schedule/index.tsx new file mode 100644 index 000000000..83bd979be --- /dev/null +++ b/src/component/Schedule/index.tsx @@ -0,0 +1,55 @@ +import { ScheduleStatus, ScheduleType } from '@/d.ts/schedule'; +import Content from './layout/Content'; +import styles from './index.less'; +import Sider from './layout/Sider'; +import { useSearchParams } from '@umijs/max'; +import login from '@/store/login'; +import { toInteger } from 'lodash'; +import { SchedulePageMode } from './interface'; +import { useEffect } from 'react'; + +interface IProps { + projectId?: number; + mode?: SchedulePageMode; +} + +const ScheduleManage: React.FC = (props) => { + const { projectId, mode = SchedulePageMode.COMMON } = props; + const [searchParams, setSearchParams] = useSearchParams(); + const defaultScheduleId = searchParams.get('scheduleId'); + const defaultScheduleType = searchParams.get('scheduleType') as ScheduleType; + const defaultOrganizationId = searchParams.get('organizationId'); + const defaultSubTaskId = searchParams.get('subTaskId'); + const defaultScheduleStatus = searchParams.get('scheduleStatus'); + const currentOrganizationId = login.organizationId; + const isOrganizationMatch = toInteger(defaultOrganizationId) === toInteger(currentOrganizationId); + + useEffect(() => { + setTimeout(() => { + searchParams.delete('scheduleId'); + searchParams.delete('scheduleType'); + searchParams.delete('organizationId'); + searchParams.delete('subTaskId'); + searchParams.delete('scheduleStatus'); + setSearchParams(searchParams); + }, 100); + }, []); + + return ( +
+
+ +
+ +
+ ); +}; + +export default ScheduleManage; diff --git a/src/component/Schedule/interface.ts b/src/component/Schedule/interface.ts new file mode 100644 index 000000000..10d537707 --- /dev/null +++ b/src/component/Schedule/interface.ts @@ -0,0 +1,111 @@ +import { + ScheduleType, + ScheduleDetailType, + IScheduleRecord, + ScheduleRecordParameters, + ScheduleStatus, +} from '@/d.ts/schedule'; +import type { Dayjs } from 'dayjs'; +import { IResponseData, TaskStatus } from '@/d.ts'; +import { ScheduleTaskStatus } from '@/d.ts/scheduleTask'; + +export enum SchedulePageMode { + COMMON = 'COMMON', + PROJECT = 'PROJECT', + MULTI_PAGE = 'MULTI_PAGE', +} + +export interface IState { + detailId: number; + scheduleType: ScheduleType; + detailVisible: boolean; + status: TaskStatus; + schedule: IResponseData>; + detailType: ScheduleDetailType; +} + +export interface ISubTaskState { + detailId: number; + detailVisible: boolean; + subTask: IResponseData; + scheduleId: number; +} + +export interface IApprovalState { + visible: boolean; + approvalStatus: boolean; + detailId: number; +} + +export enum ScheduleSearchType { + SCHEDULENAME = 'SCHEDULENAME', + SCHEDULEID = 'SCHEDULEID', + CREATOR = 'CREATOR', + // DATABASE = 'DATABASE', + DATASOURCE = 'DATASOURCE', + CLUSTER = 'CLUSTER', + TENANT = 'TENANT', +} + +export enum SubTaskSearchType { + ID = 'ID', + SCHEDULENAME = 'SCHEDULENAME', + SCHEDULEID = 'SCHEDULEID', + CREATOR = 'CREATOR', + DATABASE = 'DATABASE', + DATASOURCE = 'DATASOURCE', + CLUSTER = 'CLUSTER', + TENANT = 'TENANT', +} + +export enum ScheduleTab { + all = 'all', + approveByCurrentUser = 'approveByCurrentUser', +} + +export enum ScheduleTaskTab { + all = ' all', + approveByCurrentUser = 'approveByCurrentUser', +} + +export enum ApprovalStatus { + APPROVING = 'APPROVING', + APPROVE_FAILED = 'APPROVE_FAILED', +} + +export enum Perspective { + /** 调度视角 */ + scheduleView = 'scheduleView', + /** 执行视角*/ + executionView = 'executionView', +} + +export interface IPagination { + current: number; + pageSize: number; +} + +export interface IScheduleParam { + searchValue: string; + searchType: ScheduleSearchType; + type: ScheduleType; + status: ScheduleStatus[]; + projectIds: number[]; + sort: string; + timeRange: number | string; + executeDate?: [Dayjs, Dayjs]; + approveStatus?: ApprovalStatus[]; + tab?: ScheduleTab; +} + +export interface ISubTaskParam { + searchValue: string; + searchType: SubTaskSearchType; + type: ScheduleType; + status: ScheduleTaskStatus[]; + projectIds: number[]; + sort: string; + timeRange: number | string; + executeDate?: [Dayjs, Dayjs]; + tab?: ScheduleTaskTab; +} diff --git a/src/component/Schedule/layout/Content.tsx b/src/component/Schedule/layout/Content.tsx new file mode 100644 index 000000000..ee8f2a58d --- /dev/null +++ b/src/component/Schedule/layout/Content.tsx @@ -0,0 +1,381 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { UserStore } from '@/store/login'; +import { ModalStore } from '@/store/modal'; +import { + ScheduleType, + SchedulePageType, + ScheduleDetailType, + ScheduleStatus, +} from '@/d.ts/schedule'; +import { inject, observer } from 'mobx-react'; +import { ScheduleStore } from '@/store/schedule'; +import { useSetState } from 'ahooks'; +import styles from '@/component/Schedule/index.less'; +import DetailModals from './ScheduleDetail'; +import type { ITableInstance } from '@/component/CommonTable/interface'; +import ScheduleTable from '../components/ScheduleTable'; +import SubTaskDetailModal from '@/component/Schedule/layout/SubTaskDetail'; +import { + IScheduleParam, + ISubTaskParam, + Perspective, + IState, + ISubTaskState, + SchedulePageMode, + ScheduleSearchType, + SubTaskSearchType, + ScheduleTab, + ApprovalStatus, +} from '../interface'; +import { + ScheduleListParams, + getSubTaskList, + getScheduleList, + SubTaskListParams, +} from '@/common/network/schedule'; +import { getDefaultParam, getDefaultSubTaskParam } from '../helper'; +import { getPreTime } from '@/util/utils'; +import { schedlueConfig } from '@/page/Schedule/const'; +import ApprovalModal from '@/component/Task/component/ApprovalModal'; +import { message } from 'antd'; +import { scheduleTask } from '@/d.ts/scheduleTask'; +import { IPagination } from '@/component/Schedule/interface'; +import { getFirstEnabledSchedule } from '../helper'; + +interface IProps { + scheduleStore?: ScheduleStore; + userStore?: UserStore; + modalStore?: ModalStore; + pageKey?: SchedulePageType; + tabHeight?: number; + projectId?: number; + defaultScheduleId?: number; + defaultScheduleType?: ScheduleType; + defaultSubTaskId?: number; + mode?: SchedulePageMode; + defaultScheduleStatus?: ScheduleStatus; +} +const Content: React.FC = (props) => { + const { + pageKey, + scheduleStore, + projectId, + userStore, + mode = SchedulePageMode.COMMON, + defaultScheduleStatus, + } = props; + /** 作业视角state */ + const [state, setState] = useSetState({ + detailId: scheduleStore?.defaultOpenScheduleId, + scheduleType: scheduleStore?.defauleOpenScheduleType, + detailVisible: !!scheduleStore?.defaultOpenScheduleId, + detailType: null, + schedule: null, + status: null, + }); + + /** 执行视角state */ + const [subTaskState, setSubTaskState] = useSetState({ + detailId: scheduleStore?.defaultOpenScheduleId, + detailVisible: !!scheduleStore?.defaultOpenScheduleId, + subTask: null, + scheduleId: null, + }); + + const [params, setParams] = useSetState(getDefaultParam()); + const [subTaskParams, setsubTaskParams] = useSetState(getDefaultSubTaskParam()); + const [perspective, setPerspective] = useState(Perspective.scheduleView); + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 0, + }); + + const [approvalState, setApprovalState] = useSetState({ + visible: false, + approvalStatus: false, + detailId: null, + }); + + const isSqlworkspace = location?.pathname?.includes('/sqlworkspace'); + const theme = isSqlworkspace ? null : 'vs'; + const tableRef = useRef(); + const scheduleTabType = useMemo(() => { + return pageKey || scheduleStore?.schedulePageType; + }, [pageKey, scheduleStore?.schedulePageType]); + const [loading, setLoading] = useState(false); + + const handleDetailVisible = ( + schedule, + visible: boolean = false, + detailType?: ScheduleDetailType, + ) => { + const detailId = schedule?.scheduleId; + setState({ + detailId, + scheduleType: schedule?.type, + detailVisible: visible, + detailType, + }); + }; + + const handleSubTaskDetailVisible = (subTask: scheduleTask, visible: boolean = false) => { + setSubTaskState({ + detailId: subTask?.id, + detailVisible: visible, + scheduleId: Number(subTask?.scheduleId), + }); + }; + + const reloadList = () => { + loadData( + perspective === Perspective.scheduleView ? params : subTaskParams, + perspective, + pagination, + ); + }; + + const handleMenuItemClick = (type: ScheduleType) => { + scheduleStore.resetScheduleCreateData(); + switch (type) { + case ScheduleType.SQL_PLAN: { + scheduleStore.setSQLPlanData(true, mode, { projectId }); + break; + } + case ScheduleType.PARTITION_PLAN: { + scheduleStore.setPartitionPlanData(true, mode, { projectId }); + break; + } + case ScheduleType.DATA_ARCHIVE: { + scheduleStore.setDataArchiveData(true, mode, { projectId }); + break; + } + case ScheduleType.DATA_DELETE: { + scheduleStore.setDataClearData(true, mode, { projectId }); + } + } + }; + + const resolveParams = (params: IScheduleParam, pagination: IPagination): ScheduleListParams => { + const { pageSize, current } = pagination ?? {}; + let apiParams: ScheduleListParams = { + dataSourceName: params.searchType === ScheduleSearchType.DATASOURCE ? params.searchValue : '', + id: + params.searchType === ScheduleSearchType.SCHEDULEID + ? Number(params.searchValue) + : undefined, + name: params.searchType === ScheduleSearchType.SCHEDULENAME ? params.searchValue : '', + creator: params.searchType === ScheduleSearchType.CREATOR ? params.searchValue : '', + clusterId: params.searchType === ScheduleSearchType.CLUSTER ? params.searchValue : '', + tenantId: params.searchType === ScheduleSearchType.TENANT ? params.searchValue : '', + type: + scheduleStore?.schedulePageType !== SchedulePageType.ALL + ? (scheduleStore?.schedulePageType as unknown as ScheduleType) + : params.type, + status: params.status?.length ? params.status : [], + sort: params.sort, + approveStatus: params.approveStatus?.length ? params.approveStatus : [], + projectIds: projectId + ? [projectId] + : params.projectIds?.length + ? (params.projectIds?.map((item) => Number(item)) as number[]) + : [], + page: current, + size: pageSize, + startTime: params.timeRange === 'ALL' ? undefined : String(getPreTime(7)), + endTime: params.timeRange === 'ALL' ? undefined : String(getPreTime(0)), + }; + if (typeof params?.timeRange === 'number') { + apiParams.startTime = String(getPreTime(params?.timeRange)); + apiParams.endTime = String(getPreTime(0)); + } + if (params?.timeRange === 'custom' && params?.executeDate?.filter(Boolean)?.length === 2) { + apiParams.startTime = String(params?.executeDate?.[0]?.valueOf()); + apiParams.endTime = String(params?.executeDate?.[1]?.valueOf()); + } + if (params.tab === ScheduleTab.approveByCurrentUser) { + apiParams.approveStatus = [ApprovalStatus.APPROVING]; + } + return apiParams; + }; + + /** 作业视角 */ + const loadList = async (params: IScheduleParam, pagination: IPagination) => { + const apiParams = resolveParams(params, pagination); + if (!apiParams.size) { + return; + } + const res = await getScheduleList(apiParams); + setLoading(false); + setState({ + schedule: res, + }); + }; + + const resolvesubTaskParams = ( + params: ISubTaskParam, + pagination: IPagination, + ): SubTaskListParams => { + const { pageSize, current } = pagination ?? {}; + let apiParams: SubTaskListParams = { + dataSourceName: + params.searchType === SubTaskSearchType.DATASOURCE ? params.searchValue : undefined, + id: params.searchType === SubTaskSearchType.ID ? Number(params.searchValue) : undefined, + scheduleName: params.searchType === SubTaskSearchType.SCHEDULENAME ? params.searchValue : '', + databaseName: params.searchType === SubTaskSearchType.DATABASE ? params.searchValue : '', + scheduleId: + params.searchType === SubTaskSearchType.SCHEDULEID ? Number(params.searchValue) : undefined, + creator: params.searchType === SubTaskSearchType.CREATOR ? params.searchValue : '', + clusterId: params.searchType === SubTaskSearchType.CLUSTER ? params.searchValue : '', + tenantId: params.searchType === SubTaskSearchType.TENANT ? params.searchValue : '', + scheduleType: params.type, + status: params.status?.length ? params.status : [], + projectIds: projectId + ? [projectId] + : params.projectIds?.length + ? (params.projectIds?.map((item) => Number(item)) as number[]) + : [], + startTime: params.timeRange === 'ALL' ? undefined : String(getPreTime(7)), + endTime: params.timeRange === 'ALL' ? undefined : String(getPreTime(0)), + page: current, + size: pageSize, + sort: params.sort, + }; + if (typeof params?.timeRange === 'number') { + apiParams.startTime = String(getPreTime(params?.timeRange)); + apiParams.endTime = String(getPreTime(0)); + } + if (params?.timeRange === 'custom' && params?.executeDate?.filter(Boolean)?.length === 2) { + apiParams.startTime = String(params?.executeDate?.[0]?.valueOf()); + apiParams.endTime = String(params?.executeDate?.[1]?.valueOf()); + } + if ( + scheduleStore?.schedulePageType && + scheduleStore?.schedulePageType !== SchedulePageType.ALL + ) { + apiParams.scheduleType = scheduleStore?.schedulePageType as unknown as ScheduleType; + } + return apiParams; + }; + + /** 执行视角 */ + const loadSubTaskList = async (params: ISubTaskParam, pagination: IPagination) => { + const apiParams = resolvesubTaskParams(params, pagination); + if (!apiParams.size) { + return; + } + const res = await getSubTaskList(apiParams); + setLoading(false); + setSubTaskState({ + subTask: res, + }); + }; + + const loadData = async ( + params: IScheduleParam | ISubTaskParam, + perspective: Perspective, + pagination: IPagination, + ) => { + if (perspective === Perspective.scheduleView) { + await loadList(params as IScheduleParam, pagination); + } else { + await loadSubTaskList(params as ISubTaskParam, pagination); + } + }; + + const handleApprovalVisible = (approvalStatus: boolean = false, id: number) => { + setApprovalState({ + detailId: id, + approvalStatus, + visible: true, + }); + }; + + const openDefaultSchedule = () => { + const { defaultScheduleId, defaultScheduleType, defaultSubTaskId } = props; + if (defaultScheduleId) { + if (!schedlueConfig[defaultScheduleType]?.enabled()) { + message.error('无当前作业查看权限'); + return; + } + if (defaultSubTaskId) { + setSubTaskState({ + detailId: defaultSubTaskId, + detailVisible: true, + scheduleId: defaultScheduleId, + }); + } else { + setState({ + detailId: defaultScheduleId, + scheduleType: defaultScheduleType, + detailVisible: true, + }); + } + } else if (defaultScheduleType) { + scheduleStore?.setSchedulePageType(defaultScheduleType as unknown as SchedulePageType); + } else { + const firstEnabledSchedule = getFirstEnabledSchedule(); + scheduleStore?.setSchedulePageType(firstEnabledSchedule?.pageType); + } + }; + + useEffect(() => { + openDefaultSchedule(); + }, []); + + return ( + <> +
+ +
+ + setSubTaskState({ detailVisible: false })} + detailId={subTaskState.detailId} + scheduleId={subTaskState?.scheduleId} + /> + setApprovalState({ visible: false })} + /> + + ); +}; + +export default inject('scheduleStore', 'modalStore')(observer(Content)); diff --git a/src/component/Schedule/layout/Header/DateSelect.tsx b/src/component/Schedule/layout/Header/DateSelect.tsx new file mode 100644 index 000000000..0c770af3e --- /dev/null +++ b/src/component/Schedule/layout/Header/DateSelect.tsx @@ -0,0 +1,134 @@ +import React, { useContext, useMemo } from 'react'; +import { ClockCircleOutlined } from '@ant-design/icons'; +import { Select, DatePicker } from 'antd'; +import FilterIcon from '@/component/Button/FIlterIcon'; +import { formatMessage } from '@/util/intl'; +import ParamsContext from '@/component/Schedule/context/ParamsContext'; +import styles from './index.less'; +import dayjs, { Dayjs } from 'dayjs'; + +const { RangePicker } = DatePicker; + +export const TIME_OPTION_ALL_TASK = 'ALL'; + +export const TimeOptions = [ + { + label: formatMessage({ id: 'odc.component.TimeSelect.LastDays', defaultMessage: '最近 7 天' }), //最近 7 天 + value: 7, + }, + + { + label: formatMessage({ + id: 'odc.component.TimeSelect.LastDays.1', + defaultMessage: '最近 15 天', + }), //最近 15 天 + value: 15, + }, + + { + label: formatMessage({ + id: 'odc.component.TimeSelect.LastDays.2', + defaultMessage: '最近 30 天', + }), //最近 30 天 + value: 30, + }, + { + label: formatMessage({ + id: 'odc.component.TimeSelect.LastSixMonths', + defaultMessage: '最近半年', + }), //最近半年 + value: 183, + }, + { + label: formatMessage({ id: 'src.component.TimeSelect.9E6CA23B', defaultMessage: '全部' }), + value: TIME_OPTION_ALL_TASK, + }, + { + label: formatMessage({ id: 'odc.component.TimeSelect.Custom', defaultMessage: '自定义' }), //自定义 + value: 'custom', + }, +]; + +const DateSelect = ({ isScheduleView }: { isScheduleView: boolean }) => { + const context = useContext(ParamsContext); + const { params, setParams, subTaskParams, setsubTaskParams } = context || {}; + const { timeRange, executeDate } = params || {}; + const { timeRange: subTaskTimeRange, executeDate: subTaskExecuteDate } = subTaskParams || {}; + + const handleChange = (value) => { + if (isScheduleView) { + setParams?.({ timeRange: value }); + } else { + setsubTaskParams?.({ timeRange: value }); + } + }; + + const handleSelectDate = (value) => { + if (isScheduleView) { + setParams?.({ executeDate: value }); + } else { + setsubTaskParams?.({ executeDate: value }); + } + }; + + const getStyle = () => { + if (!isScheduleView) { + if (subTaskTimeRange === 'custom') { + return { height: '20px' }; + } else { + } + return { height: '20px', width: '100%' }; + } + if (timeRange === 'custom') { + return { height: '20px' }; + } + return { height: '20px', width: '100%' }; + }; + + const showRangePicker = useMemo(() => { + if (isScheduleView) { + return timeRange === 'custom'; + } else { + return subTaskTimeRange === 'custom'; + } + }, [isScheduleView, timeRange, subTaskTimeRange]); + + return ( + +
+ + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + value={status} + mode="multiple" + options={statusOptions || []} + style={{ width: '100%' }} + onChange={handleSelectStatus} + popupRender={(menu) => { + return ( + <> + {menu} + +
+ + {status?.length ? ( + + ) : ( + '' + )} +
+ + ); + }} + allowClear + /> + + ); +}; + +export default ScheduleStatusFilter; diff --git a/src/component/Schedule/layout/Header/Filter/ScheduleTaskStatusFilter.tsx b/src/component/Schedule/layout/Header/Filter/ScheduleTaskStatusFilter.tsx new file mode 100644 index 000000000..bdb23b442 --- /dev/null +++ b/src/component/Schedule/layout/Header/Filter/ScheduleTaskStatusFilter.tsx @@ -0,0 +1,68 @@ +import ParamsContext from '@/component/Schedule/context/ParamsContext'; +import { useContext, useMemo, useState } from 'react'; +import { Button, Divider, Select } from 'antd'; +import { ScheduleTaskStatusTextMap } from '@/constant/scheduleTask'; +import { ScheduleTaskStatus } from '@/d.ts/scheduleTask'; + +const ScheduleTaskStatusFilter = () => { + const context = useContext(ParamsContext); + const { subTaskParams, setsubTaskParams } = context || {}; + const { status } = subTaskParams || {}; + + const statusOptions = useMemo(() => { + return Object.keys(ScheduleTaskStatus).map((item) => { + return { + label: ScheduleTaskStatusTextMap?.[item], + value: item, + }; + }); + }, []); + + const handleSelectStatus = (value) => { + setsubTaskParams?.({ status: value }); + }; + + return ( + <> +
任务状态
+ + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + options={scheduleTypeOptions || []} + style={{ width: '100%' }} + value={selectValue} + allowClear + onChange={handleSelectType} + /> + + ); +}; + +export default ScheduleTypeFilter; diff --git a/src/component/Schedule/layout/Header/Filter/approvalStatusFilter.tsx b/src/component/Schedule/layout/Header/Filter/approvalStatusFilter.tsx new file mode 100644 index 000000000..45400f7ea --- /dev/null +++ b/src/component/Schedule/layout/Header/Filter/approvalStatusFilter.tsx @@ -0,0 +1,45 @@ +import ParamsContext from '@/component/Schedule/context/ParamsContext'; +import { useContext, useMemo } from 'react'; +import { Select } from 'antd'; +import { ApprovalStatus } from '@/component/Schedule/interface'; +import { ApprovalStatusTextMap } from '@/constant/schedule'; + +const ApprovalStatusFilter = () => { + const context = useContext(ParamsContext); + const { params, setParams } = context || {}; + const { approveStatus } = params || {}; + + const handleSelectApprovalStatus = (value) => { + setParams?.({ approveStatus: value }); + }; + + const ApprovalStatusOptions = useMemo(() => { + return Object.keys(ApprovalStatus).map((item) => { + return { + label: ApprovalStatusTextMap?.[item], + value: item, + }; + }); + }, []); + + return ( + <> +
审批状态
+ + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + value={selectValue} + mode="multiple" + options={projectOptions} + style={{ width: '100%' }} + onChange={handleSelectProject} + allowClear + /> + + ); +}; + +export default ProjectFilter; diff --git a/src/component/Schedule/layout/Header/Search.tsx b/src/component/Schedule/layout/Header/Search.tsx new file mode 100644 index 000000000..6ed50e1d7 --- /dev/null +++ b/src/component/Schedule/layout/Header/Search.tsx @@ -0,0 +1,49 @@ +import { useContext } from 'react'; +import ParamsContext from '@/component/Schedule/context/ParamsContext'; +import { ScheduleSearchType } from '@/component/Schedule/interface'; +import { formatMessage } from '@/util/intl'; +import InputSelect from '@/component/InputSelect'; + +const ScheduleSearchTypeText = { + [ScheduleSearchType.SCHEDULENAME]: '作业名称', + [ScheduleSearchType.SCHEDULEID]: '作业ID', + [ScheduleSearchType.CREATOR]: '创建人', + [ScheduleSearchType.DATASOURCE]: formatMessage({ + id: 'odc.component.RecordPopover.column.DataSource', + defaultMessage: '数据源', + }), //数据源 + [ScheduleSearchType.CLUSTER]: formatMessage({ + id: 'odc.Connecion.ConnectionList.ParamContext.Cluster', + defaultMessage: '集群', + }), //集群 + [ScheduleSearchType.TENANT]: formatMessage({ + id: 'odc.Connecion.ConnectionList.ParamContext.Tenant', + defaultMessage: '租户', + }), //租户 +}; + +const Search = () => { + const context = useContext(ParamsContext); + const { params, setParams } = context; + const { searchValue, searchType } = params || {}; + const selectTypeOptions = Object.keys(ScheduleSearchType).map((item) => ({ + value: item, + label: ScheduleSearchTypeText[item], + })); + + return ( + { + setParams({ + searchValue, + searchType: searchType as ScheduleSearchType, + }); + }} + /> + ); +}; + +export default Search; diff --git a/src/component/Schedule/layout/Header/Segment.tsx b/src/component/Schedule/layout/Header/Segment.tsx new file mode 100644 index 000000000..694de8ceb --- /dev/null +++ b/src/component/Schedule/layout/Header/Segment.tsx @@ -0,0 +1,53 @@ +import { Segmented, Tooltip } from 'antd'; +import { useContext } from 'react'; +import { Perspective } from '@/component/Schedule/interface'; +import ParamsContext from '@/component/Schedule/context/ParamsContext'; + +const Segment = () => { + const context = useContext(ParamsContext); + const { perspective, setPerspective, setLoading } = context; + return ( + { + setLoading(true); + setPerspective(value); + }} + options={[ + { + value: Perspective.scheduleView, + icon: ( + +
作业视角
+
展示所有作业创建记录
+ + } + > + 作业视角 +
+ ), + }, + + { + value: Perspective.executionView, + icon: ( + +
执行视角
+
展示所有作业的任务执行记录
+ + } + > + 执行视角 +
+ ), + }, + ]} + /> + ); +}; + +export default Segment; diff --git a/src/component/Schedule/layout/Header/Sort.tsx b/src/component/Schedule/layout/Header/Sort.tsx new file mode 100644 index 000000000..a8f5e53a5 --- /dev/null +++ b/src/component/Schedule/layout/Header/Sort.tsx @@ -0,0 +1,77 @@ +import React, { useContext, useMemo } from 'react'; +import { Dropdown } from 'antd'; +import { inject, observer } from 'mobx-react'; +import type { MenuProps } from 'antd'; +import { SortAscendingOutlined } from '@ant-design/icons'; +import FilterIcon from '@/component/Button/FIlterIcon'; +import ParamsContext from '@/component/Schedule/context/ParamsContext'; + +interface IProps {} + +const items: MenuProps['items'] = [ + { + key: 'createTime,asc', + label: '按创建时间排序', + }, + { + key: 'createTime,desc', + label: '按创建时间降序', + }, +]; + +const subTaskItems: MenuProps['items'] = [ + { + key: 'fireTime,asc', + label: '按执行时间排序', + }, + { + key: 'fireTime,desc', + label: '按执行时间降序', + }, +]; + +const Sorter: React.FC = function (props) { + const context = useContext(ParamsContext); + const { params, setParams, isScheduleView, subTaskParams, setsubTaskParams } = context; + const { sort } = params || {}; + const { sort: subTaskSort } = subTaskParams || {}; + + const sortKey = useMemo(() => { + return isScheduleView ? [sort] : [subTaskSort]; + }, [isScheduleView, sort, subTaskSort]); + + const handleChangeSort = (e) => { + if (isScheduleView) { + setParams({ sort: e.key }); + } else { + setsubTaskParams({ sort: e.key }); + } + }; + + const options = useMemo(() => { + return isScheduleView ? items : subTaskItems; + }, [isScheduleView]); + + const isActive = useMemo(() => { + if (isScheduleView) { + return Boolean(sort); + } else { + return Boolean(subTaskSort); + } + }, [sort, subTaskSort, isScheduleView]); + + return ( + + + + + + ); +}; +export default inject('userStore')(observer(Sorter)); diff --git a/src/component/Schedule/layout/Header/SubTaskSearch.tsx b/src/component/Schedule/layout/Header/SubTaskSearch.tsx new file mode 100644 index 000000000..aa448a4d6 --- /dev/null +++ b/src/component/Schedule/layout/Header/SubTaskSearch.tsx @@ -0,0 +1,54 @@ +import { SubTaskSearchType } from '@/component/Schedule/interface'; +import InputSelect from '@/component/InputSelect'; +import { formatMessage } from '@/util/intl'; +import { useContext, useState } from 'react'; +import ParamsContext from '@/component/Schedule/context/ParamsContext'; + +const ScheduleSearchTypeText = { + [SubTaskSearchType.ID]: '执行记录 ID', + [SubTaskSearchType.SCHEDULENAME]: '作业名称', + [SubTaskSearchType.SCHEDULEID]: '作业ID', + [SubTaskSearchType.CREATOR]: '创建人', + [SubTaskSearchType.DATABASE]: formatMessage({ + id: 'src.component.ODCSetting.config.9EC92943', + defaultMessage: '数据库', + }), //'数据库' + [SubTaskSearchType.DATASOURCE]: formatMessage({ + id: 'odc.component.RecordPopover.column.DataSource', + defaultMessage: '数据源', + }), //数据源 + [SubTaskSearchType.CLUSTER]: formatMessage({ + id: 'odc.Connecion.ConnectionList.ParamContext.Cluster', + defaultMessage: '集群', + }), //集群 + [SubTaskSearchType.TENANT]: formatMessage({ + id: 'odc.Connecion.ConnectionList.ParamContext.Tenant', + defaultMessage: '租户', + }), //租户 +}; + +const Search = () => { + const context = useContext(ParamsContext); + const { subTaskParams, setsubTaskParams } = context; + const selectTypeOptions = Object.keys(SubTaskSearchType).map((item) => ({ + value: item, + label: ScheduleSearchTypeText[item], + })); + const { searchValue, searchType } = subTaskParams || {}; + + return ( + { + setsubTaskParams({ + searchValue, + searchType: searchType as SubTaskSearchType, + }); + }} + /> + ); +}; + +export default Search; diff --git a/src/component/Schedule/layout/Header/SubTaskTab.tsx b/src/component/Schedule/layout/Header/SubTaskTab.tsx new file mode 100644 index 000000000..4b8b65ee2 --- /dev/null +++ b/src/component/Schedule/layout/Header/SubTaskTab.tsx @@ -0,0 +1,33 @@ +import React, { useContext } from 'react'; +import { Radio } from 'antd'; +import { ScheduleTaskTab } from '@/component/Schedule/interface'; +import ParamsContext from '@/component/Schedule/context/ParamsContext'; + +const SubTaskTab = () => { + const context = useContext(ParamsContext); + const { subTaskParams, setsubTaskParams } = context || {}; + + const handleSelect = (e) => { + setsubTaskParams?.({ tab: e.target.value as ScheduleTaskTab }); + }; + + return ( + + ); +}; +export default SubTaskTab; diff --git a/src/component/Schedule/layout/Header/Tabs.tsx b/src/component/Schedule/layout/Header/Tabs.tsx new file mode 100644 index 000000000..7fee0fe93 --- /dev/null +++ b/src/component/Schedule/layout/Header/Tabs.tsx @@ -0,0 +1,33 @@ +import React, { useContext } from 'react'; +import { Radio } from 'antd'; +import { ScheduleTab } from '@/component/Schedule/interface'; +import ParamsContext from '@/component/Schedule/context/ParamsContext'; + +const Tabs = () => { + const context = useContext(ParamsContext); + const { params, setParams } = context || {}; + + const handleSelect = (e) => { + setParams?.({ tab: e.target.value as ScheduleTab }); + }; + + return ( + + ); +}; +export default Tabs; diff --git a/src/component/Schedule/layout/Header/index.less b/src/component/Schedule/layout/Header/index.less new file mode 100644 index 000000000..ef21c0038 --- /dev/null +++ b/src/component/Schedule/layout/Header/index.less @@ -0,0 +1,23 @@ +.segmented { + :global { + .ant-segmented-item-selected { + color: var(--icon-blue-color); + } + } +} + +.timeSelect { + :global { + .ant-select-selector { + padding: 0px 4px !important; + width: 100% !important; + } + .ant-select-arrow { + margin-left: 8px; + } + } +} + +.ml6 { + margin-left: 6px; +} diff --git a/src/component/Schedule/layout/Header/index.tsx b/src/component/Schedule/layout/Header/index.tsx new file mode 100644 index 000000000..5c2ef09bc --- /dev/null +++ b/src/component/Schedule/layout/Header/index.tsx @@ -0,0 +1,29 @@ +import { Space } from 'antd'; +import { ReloadOutlined } from '@ant-design/icons'; +import FilterIcon from '@/component/Button/FIlterIcon'; +import Sort from '@/component/Schedule/layout/Header/Sort'; +import Tabs from './Tabs'; +import Segment from '@/component/Schedule/layout/Header/Segment'; +import Search from './Search'; +import Filter from './Filter/index'; +import ParamsContext from '@/component/Schedule/context/ParamsContext'; +import { useContext } from 'react'; +import SubTaskSearch from './SubTaskSearch'; + +const Header = () => { + const context = useContext(ParamsContext); + + return ( + + {context.isScheduleView ? : } + {context.isScheduleView ? : undefined} + + + + context?.reload?.()} /> + + + + ); +}; +export default Header; diff --git a/src/component/Schedule/layout/ScheduleDetail.tsx b/src/component/Schedule/layout/ScheduleDetail.tsx new file mode 100644 index 000000000..5f1465e88 --- /dev/null +++ b/src/component/Schedule/layout/ScheduleDetail.tsx @@ -0,0 +1,170 @@ +import { getScheduleDetail } from '@/common/network/schedule'; +import type { ITableLoadOptions } from '@/component/CommonTable/interface'; +import ScheduleDetailModal from '../components/ScheduleDetailModal'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { + DataClearScheduleContent, + PartitionScheduleContent, + DataArchiveScheduleContent, + SQLPlanScheduleContent, +} from '../modals/Detail'; +import { + ScheduleType, + SchedulePageType, + IScheduleRecord, + ScheduleRecordParameters, + IPartitionPlan, + IDataArchiveParameters, + ScheduleStatus, + IDataClearParameters, + ISqlPlanParameters, +} from '@/d.ts/schedule'; +import { ScheduleDetailType } from '@/d.ts/schedule'; +import { SchedulePageMode } from '../interface'; +import { useLoop } from '@/util/hooks/useLoop'; +import scheduleStore from '@/store/schedule'; + +const loopStatus = [ + ScheduleStatus.ENABLED, + ScheduleStatus.PAUSE, + ScheduleStatus.COMPLETED, + ScheduleStatus.TERMINATED, +]; +interface IProps { + taskOpenRef?: React.RefObject; + type: ScheduleType; + detailId: number; + visible: boolean; + enabledAction?: boolean; + theme?: string; + detailType?: ScheduleDetailType; + onReloadList?: () => void; + onDetailVisible: ( + schedule: IScheduleRecord, + visible: boolean, + detailType?: ScheduleDetailType, + ) => void; + onApprovalVisible?: (status: boolean, id: number) => void; + mode?: SchedulePageMode; +} + +const ScheduleDetail: React.FC = React.memo((props) => { + const { + visible, + detailId, + enabledAction = true, + theme, + onApprovalVisible, + detailType: propsDetailType, + mode, + } = props; + const [schedule, setSchedule] = useState>(null); + const [detailType, setDetailType] = useState(ScheduleDetailType.INFO); + const [loading, setLoading] = useState(false); + let taskContent = null; + + const { loop: getSchedule, destory } = useLoop((count) => { + return async () => { + if (schedule?.status && loopStatus?.includes(schedule?.status)) { + destory(); + return; + } + const data = await getScheduleDetail(detailId); + setLoading(false); + if (data) { + setSchedule(data); + } + }; + }, 6500); + + useEffect(() => { + if (propsDetailType) { + setDetailType(propsDetailType); + } + }, [propsDetailType]); + + const loadCycleTaskData = async (args?: ITableLoadOptions) => { + getSchedule(); + }; + + const resetModal = () => { + setSchedule(null); + setDetailType(ScheduleDetailType.INFO); + scheduleStore.setOpenOperationId(null); + }; + + useEffect(() => { + if (visible && detailId) { + loadCycleTaskData(); + } + }, [detailId, visible]); + + useEffect(() => { + if (!visible) { + resetModal(); + destory(); + } + }, [visible]); + + useEffect(() => { + if (visible && detailId && !schedule) { + setLoading(true); + } + }, [schedule, visible, detailId]); + + const handleDetailTypeChange = (type: ScheduleDetailType) => { + setDetailType(type); + }; + + const onClose = () => { + props.onDetailVisible(null, false); + }; + + switch (schedule?.type) { + case ScheduleType.PARTITION_PLAN: { + taskContent = ( + } /> + ); + break; + } + + case ScheduleType.DATA_ARCHIVE: { + taskContent = ( + } + /> + ); + break; + } + case ScheduleType.DATA_DELETE: { + taskContent = ( + } /> + ); + break; + } + case ScheduleType.SQL_PLAN: { + taskContent = ( + } /> + ); + break; + } + } + + return ( + + ); +}); + +export default ScheduleDetail; diff --git a/src/component/Schedule/layout/Sider.tsx b/src/component/Schedule/layout/Sider.tsx new file mode 100644 index 000000000..130562a81 --- /dev/null +++ b/src/component/Schedule/layout/Sider.tsx @@ -0,0 +1,60 @@ +import React, { useEffect } from 'react'; +import type { PageStore } from '@/store/page'; +import { ScheduleStore } from '@/store/schedule'; +import { inject, observer } from 'mobx-react'; +import { SchedulePageType } from '@/d.ts/schedule'; +import { openSchedulesPage } from '@/store/helper/page'; +import { schedlueConfig } from '@/page/Schedule/const'; +import styles from '@/component/Schedule/index.less'; +import classNames from 'classnames'; +import { Tooltip, Typography } from 'antd'; +import { SchedulePageMode } from '../interface'; +const { Text } = Typography; + +interface IProps { + scheduleStore?: ScheduleStore; + pageStore?: PageStore; + className?: string; + mode: SchedulePageMode; +} +const Sider: React.FC = (props) => { + const { scheduleStore, pageStore, className, mode } = props; + const pageKey = + mode === SchedulePageMode.MULTI_PAGE + ? pageStore?.activePageKey + : scheduleStore?.schedulePageType; + + const handleClick = (value: SchedulePageType) => { + if (mode === SchedulePageMode.MULTI_PAGE) { + openSchedulesPage(value); + } + scheduleStore.setSchedulePageType(value); + scheduleStore.setSelectedRowKeys([]); + }; + + return ( +
+ {Object.values(schedlueConfig).map((item) => { + if (!item.enabled()) return; + return ( +
handleClick(item.pageType)} + > + + {item.label} + +
+ ); + })} +
+ ); +}; + +export default inject('scheduleStore', 'pageStore')(observer(Sider)); diff --git a/src/component/Schedule/layout/SubTaskDetail.tsx b/src/component/Schedule/layout/SubTaskDetail.tsx new file mode 100644 index 000000000..382145ec1 --- /dev/null +++ b/src/component/Schedule/layout/SubTaskDetail.tsx @@ -0,0 +1,184 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { detailScheduleTask, getScheduleDetail } from '@/common/network/schedule'; +import { + IScheduleRecord, + ScheduleRecordParameters, + IPartitionPlan, + ScheduleType, + ISqlPlanParameters, + IDataArchiveParameters, + IDataClearParameters, +} from '@/d.ts/schedule'; +import { IScheduleTaskRecord, scheduleTask, ScheduleTaskStatus } from '@/d.ts/scheduleTask'; +import { ScheduleTaskDetailType, SubTaskType } from '@/d.ts/scheduleTask'; +import type { ILog } from '@/component/Task/component/Log'; +import { ITaskResult, Operation, CommonTaskLogType } from '@/d.ts'; +import SubTaskDetailModal from '../components/SubTaskDetailModal'; +import { getScheduleTaskLog } from '@/common/network/schedule'; +import { + DataClearScheduleContent, + PartitionScheduleContent, + DataArchiveScheduleContent, + SQLPlanScheduleContent, +} from '../modals/Detail'; +import { useLoop } from '@/util/hooks/useLoop'; + +const loopStatus = [ + ScheduleTaskStatus.PREPARING, + ScheduleTaskStatus.RUNNING, + ScheduleTaskStatus.ABNORMAL, + ScheduleTaskStatus.PAUSING, + ScheduleTaskStatus.PAUSED, + ScheduleTaskStatus.RESUMING, + ScheduleTaskStatus.CANCELING, +]; + +interface IProps { + visible: boolean; + detailId: number; + onClose: () => void; + scheduleId: number; +} + +const SubTaskDetail: React.FC = (props) => { + const { visible, onClose, detailId, scheduleId } = props; + const [subTask, setSubTask] = useState(null); + const [detailType, setDetailType] = useState(ScheduleTaskDetailType.INFO); + const [log, setLog] = useState(null); + const [result, setResult] = useState(null); + const [logType, setLogType] = useState(CommonTaskLogType.ALL); + const [loading, setLoading] = useState(false); + const [opRecord, setOpRecord] = useState(null); + const [schedule, setSchedule] = useState>(null); + let taskContent = null; + + const { loop: loadData, destory } = useLoop((count) => { + return async () => { + if (subTask?.status && loopStatus?.includes(subTask?.status)) { + destory(); + return; + } + const res = await detailScheduleTask(scheduleId, detailId); + setSubTask(res); + }; + }, 6500); + + const getLog = async () => { + const data = await getScheduleTaskLog(schedule?.scheduleId, detailId, logType); + setLoading(false); + setLog({ + ...log, + [logType]: data, + }); + }; + + const loadTaskData = async () => { + switch (detailType) { + case ScheduleTaskDetailType.INFO: { + if (!subTask) { + loadData(); + } + break; + } + case ScheduleTaskDetailType.LOG: { + getLog(); + break; + } + } + }; + + const handleLogTypeChange = (type: CommonTaskLogType) => { + setLogType(type); + }; + + const resetModal = () => { + setSubTask(null); + setDetailType(ScheduleTaskDetailType.INFO); + setLog(null); + setResult(null); + setSchedule(null); + destory(); + }; + + const getSchedule = async () => { + const res = await getScheduleDetail(scheduleId); + setSchedule(res); + }; + + useEffect(() => { + if (visible && detailId) { + loadTaskData(); + } else { + resetModal(); + } + }, [detailId, detailType, logType, visible]); + + useEffect(() => { + if (visible && detailId && !subTask) { + setLoading(true); + } + }, [subTask, visible, detailId]); + + useEffect(() => { + if (visible && scheduleId) { + getSchedule(); + } + }, [scheduleId, visible]); + + switch (subTask?.type) { + case SubTaskType.SQL_PLAN: + taskContent = ( + } + subTask={subTask} + /> + ); + break; + case SubTaskType.PARTITION_PLAN: + taskContent = ( + } + subTask={subTask} + /> + ); + break; + case SubTaskType.DATA_ARCHIVE: + case SubTaskType.DATA_ARCHIVE_ROLLBACK: + case SubTaskType.DATA_ARCHIVE_DELETE: + taskContent = ( + } + subTask={subTask} + /> + ); + break; + case SubTaskType.DATA_DELETE: + taskContent = ( + } + subTask={subTask} + /> + ); + break; + } + + return ( + + ); +}; + +export default SubTaskDetail; diff --git a/src/component/Schedule/modals/Create.tsx b/src/component/Schedule/modals/Create.tsx new file mode 100644 index 000000000..eebdbb60e --- /dev/null +++ b/src/component/Schedule/modals/Create.tsx @@ -0,0 +1,58 @@ +import useURLParams from '@/util/hooks/useUrlParams'; +import { useEffect, useState } from 'react'; +import { ScheduleType } from '@/d.ts/schedule'; +import DataArchive from './DataArchive/Create'; +import PartitionPlan from './PartitionPlan/Create'; +import SQLPlan from './SQLPlan/Create'; +import DataClear from './DataClear/Create'; +import classNames from 'classnames'; +import { CreateScheduleContext } from '../context/createScheduleContext'; +import { IDatabase } from '@/d.ts/database'; +import { SchedulePageMode } from '../interface'; +import { useParams } from '@umijs/max'; +import { toInteger } from 'lodash'; + +interface IProps { + type?: ScheduleType; + mode?: SchedulePageMode; +} +const CreatePage: React.FC = ({ type: propsType, mode = SchedulePageMode.COMMON }) => { + const { getParam } = useURLParams(); + const type = propsType || (getParam('type') as ScheduleType); + const [createType, setCreateType] = useState(); + const [createScheduleDatabase, setCreateScheduleDatabase] = useState(); + const { id: projectId } = useParams<{ id: string }>(); + useEffect(() => { + if (type && Object.values(ScheduleType).includes(type)) { + setCreateType(type); + } else { + setCreateType(ScheduleType.DATA_ARCHIVE); + } + }, [type]); + + return ( + <> + + {createType === ScheduleType.DATA_ARCHIVE && ( + + )} + {createType === ScheduleType.DATA_DELETE && ( + + )} + {createType === ScheduleType.PARTITION_PLAN && ( + + )} + {createType === ScheduleType.SQL_PLAN && ( + + )} + + + ); +}; + +export default CreatePage; diff --git a/src/component/Task/modals/DataArchiveTask/DetailContent/ArchiveRange.tsx b/src/component/Schedule/modals/DataArchive/Content/ArchiveRange.tsx similarity index 82% rename from src/component/Task/modals/DataArchiveTask/DetailContent/ArchiveRange.tsx rename to src/component/Schedule/modals/DataArchive/Content/ArchiveRange.tsx index e443d414a..d759af974 100644 --- a/src/component/Task/modals/DataArchiveTask/DetailContent/ArchiveRange.tsx +++ b/src/component/Schedule/modals/DataArchive/Content/ArchiveRange.tsx @@ -1,19 +1,3 @@ -/* - * 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 DisplayTable from '@/component/DisplayTable'; import { formatMessage } from '@/util/intl'; import { Flex, Popover, Tooltip, Typography } from 'antd'; diff --git a/src/component/Schedule/modals/DataArchive/Content/index.tsx b/src/component/Schedule/modals/DataArchive/Content/index.tsx new file mode 100644 index 000000000..5da03ccae --- /dev/null +++ b/src/component/Schedule/modals/DataArchive/Content/index.tsx @@ -0,0 +1,349 @@ +import { IScheduleRecord, IDataArchiveParameters } from '@/d.ts/schedule'; +import { Descriptions, Collapse, Space, Divider, message } from 'antd'; +import RiskLevelLabel from '@/component/RiskLevelLabel'; +import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; +import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; +import { formatMessage } from '@/util/intl'; +import VariableConfigTable from '@/component/Task/component/VariableConfigTable'; +import ArchiveRange from './ArchiveRange'; +import { isConnectTypeBeFileSystemGroup } from '@/util/connection'; +import { + DirtyRowActionEnum, + DirtyRowActionLabelMap, +} from '@/component/ExecuteSqlDetailModal/constant'; +import { kbToMb, mbToKb } from '@/util/utils'; +import { getFormatDateTime, milliSecondsToHour } from '@/util/utils'; +import { InsertActionOptions } from '@/component/Schedule/modals/DataArchive/Create'; +import { shardingStrategyOptions } from '@/component/Task/component/ShardingStrategyItem'; +import { SyncTableStructureConfig } from '@/component/Task/const'; +import ThrottleEditableCell from '@/component/Task/component/ThrottleEditableCell'; +import setting from '@/store/setting'; +import { updateLimiterConfig } from '@/common/network/schedule'; +import { IScheduleTaskRecord, scheduleTask } from '@/d.ts/scheduleTask'; +import { SubTypeTextMap } from '@/constant/scheduleTask'; +import EllipsisText from '@/component/EllipsisText'; +interface IProps { + schedule: IScheduleRecord; + subTask?: scheduleTask; + onReload?: () => void; +} +const DataArchiveScheduleContent: React.FC = (props) => { + const { schedule, onReload, subTask } = props; + const { parameters } = schedule || {}; + const insertActionLabel = InsertActionOptions?.find( + (item) => item.value === parameters?.migrationInsertAction, + )?.label; + + const handleRowLimit = async (rowLimit, handleClose) => { + const res = await updateLimiterConfig(schedule?.scheduleId, { + rowLimit, + }); + if (res) { + message.success( + formatMessage({ + id: 'odc.src.component.Task.DataArchiveTask.DetailContent.SuccessfullyModified', + defaultMessage: '修改成功!', + }), //'修改成功!' + ); + handleClose(); + onReload?.(); + } + }; + + const handleDataSizeLimit = async (dataSizeLimit, handleClose) => { + const res = await updateLimiterConfig(schedule?.scheduleId, { + dataSizeLimit: mbToKb(dataSizeLimit), + }); + if (res) { + message.success( + formatMessage({ + id: 'odc.src.component.Task.DataArchiveTask.DetailContent.SuccessfullyModified.1', + defaultMessage: '修改成功!', + }), //'修改成功!' + ); + handleClose(); + onReload(); + } + }; + + return ( + <> + + {subTask && ( + <> + {subTask?.id} + {SubTypeTextMap[subTask?.type]} + + )} + {!subTask && ( + <> + {schedule?.scheduleId} + 数据清理 + + )} + + +
+ +
+ } + /> +
+
+
+ + + + +
+ +
+ } + /> +
+
+
+ + + + + + +
+ + + +
+ } + direction="column" + /> + + + + } + direction="column" + /> + + + { + parameters?.deleteAfterMigration + ? formatMessage({ + id: 'odc.DataArchiveTask.DetailContent.Yes', + defaultMessage: '是', + }) //是 + : formatMessage({ + id: 'odc.DataArchiveTask.DetailContent.No', + defaultMessage: '否', + }) //否 + } + + {isConnectTypeBeFileSystemGroup(parameters?.targetDatabase?.connectType) && ( + + { + parameters?.deleteTemporaryTable + ? formatMessage({ + id: 'odc.DataArchiveTask.DetailContent.Yes', + defaultMessage: '是', + }) //是 + : formatMessage({ + id: 'odc.DataArchiveTask.DetailContent.No', + defaultMessage: '否', + }) //否 + } + + )} + {parameters?.deleteAfterMigration ? ( + + {DirtyRowActionLabelMap[parameters?.dirtyRowAction]} + + ) : null} + {parameters?.dirtyRowAction === DirtyRowActionEnum.SKIP ? ( + + {formatMessage( + { + id: 'src.component.Task.DataArchiveTask.DetailContent.A96E9271', + defaultMessage: '{LogicalExpression0} 行', + }, + { LogicalExpression0: parameters?.maxAllowedDirtyRowCount || 0 }, + )} + + ) : null} + + + {insertActionLabel || '-'} + + + {shardingStrategyOptions.find((item) => item.value === parameters?.shardingStrategy) + ?.label || '-'} + + + {parameters?.timeoutMillis ? milliSecondsToHour(parameters?.timeoutMillis) + 'h' : '-'} + + + {parameters?.syncTableStructure?.length + ? formatMessage({ + id: 'src.component.Task.DataArchiveTask.DetailContent.FFC5907D', + defaultMessage: '是', + }) + : formatMessage({ + id: 'src.component.Task.DataArchiveTask.DetailContent.855EA40A', + defaultMessage: '否', + })} + + + {parameters?.syncTableStructure && parameters?.syncTableStructure?.length + ? parameters?.syncTableStructure + ?.map((i) => { + return SyncTableStructureConfig[i].label; + }) + .join(',') + : '-'} + + + + + + + + + + + + + {schedule?.creator?.name || '-'} + + + {getFormatDateTime(schedule?.createTime)} + + + + ); +}; + +export default DataArchiveScheduleContent; diff --git a/src/component/Task/modals/DataArchiveTask/CreateModal/ArchiveRange.tsx b/src/component/Schedule/modals/DataArchive/Create/ArchiveRange.tsx similarity index 97% rename from src/component/Task/modals/DataArchiveTask/CreateModal/ArchiveRange.tsx rename to src/component/Schedule/modals/DataArchive/Create/ArchiveRange.tsx index 5ff6d1004..b254c7a7a 100644 --- a/src/component/Task/modals/DataArchiveTask/CreateModal/ArchiveRange.tsx +++ b/src/component/Schedule/modals/DataArchive/Create/ArchiveRange.tsx @@ -20,7 +20,7 @@ import { PlusOutlined, SettingOutlined, SettingFilled } from '@ant-design/icons' import { Button, Checkbox, Form, Input, Radio, Select, Typography, Tooltip } from 'antd'; import classNames from 'classnames'; import { useEffect, useState } from 'react'; -import ArchiveRangeTip from '@/component/Task/component/ArchiveRangeTip'; +import ArchiveRangeTip from '@/component/Schedule/components/ArchiveRangeTip'; import { PartitionTextArea } from '@/component/Task/component/PartitionTextArea'; import { IArchiveRange } from './index'; import styles from './index.less'; @@ -74,14 +74,7 @@ const ArchiveRange: React.FC = (props) => { return ( <> - + { - var reg = /([+-])?(\d+)?(.+)?/; - return value?.map(({ name, pattern }) => { - const [format, _pattern] = pattern?.split('|'); - let patternValue = { - operator: '', - step: '', - unit: '', - }; - if (_pattern) { - const res = _pattern?.match(reg); - const operator = res[1] ?? ''; - const step = res[2] ?? ''; - const unit = timeUnitOptions.map((item) => item.value).includes(res[3]) ? res[3] : ''; - patternValue = { - operator, - step, - unit, - }; - } - return { - name, - format, - pattern: [patternValue], - }; - }); +const defaultValue = { + triggerStrategy: TaskExecStrategy.START_NOW, + archiveRange: IArchiveRange.PORTION, + tables: [null], + migrationInsertAction: MigrationInsertAction.INSERT_DUPLICATE_UPDATE, + shardingStrategy: ShardingStrategy.AUTO, + rowLimit: 100, + dataSizeLimit: 1, }; -const CreateModal: React.FC = (props) => { - const { modalStore, projectId } = props; + +interface IProps { + scheduleStore?: ScheduleStore; + pageStore?: PageStore; + projectId?: number; + mode?: SchedulePageMode; +} +const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) => { const [previewModalVisible, setPreviewModalVisible] = useState(false); const [previewSql, setPreviewSQL] = useState(''); const [hasEdit, setHasEdit] = useState(false); @@ -191,28 +135,31 @@ const CreateModal: React.FC = (props) => { const [form] = Form.useForm(); const databaseId = Form.useWatch('databaseId', form); const { session: sourceDBSession, database: sourceDB } = useDBSession(databaseId); - - const { run: fetchCycleTaskDetail, loading } = useRequest(getCycleTaskDetail, { manual: true }); - + const { run: fetchScheduleDetail, loading } = useRequest(getScheduleDetail, { manual: true }); const loadTables = async () => { const tables = await getTableListByDatabaseName(sourceDBSession?.sessionId, sourceDB?.name); setTables(tables); }; + const { createScheduleDatabase, setCreateScheduleDatabase } = + useContext(CreateScheduleContext) || {}; + const crontabRef = useRef<{ setValue: (value: ICrontab) => void; resetFields: () => void; }>(); - const { dataArchiveVisible, dataArchiveTaskData } = modalStore; - const dataArchiveEditId = dataArchiveTaskData?.id; - const isEdit = !!dataArchiveEditId && dataArchiveTaskData?.type === 'EDIT'; + const { dataArchiveData } = scheduleStore; + const dataArchiveEditId = dataArchiveData?.id; + const isEdit = !!dataArchiveEditId && dataArchiveData?.type === 'EDIT'; const [isdeleteAfterMigration, setIsdeleteAfterMigration] = useState(false); + const loadEditData = async (editId: number) => { - const data = (await fetchCycleTaskDetail(editId)) as CycleTaskDetail; + const dataRes = (await fetchScheduleDetail(editId)) as IScheduleRecord; + setCreateScheduleDatabase(dataRes?.parameters?.sourceDatabase); const { - jobParameters, - description, + parameters, + scheduleName, triggerConfig: { triggerStrategy, cronExpression, hours, days, startAt }, - } = data; + } = dataRes; const { targetDataBaseId, @@ -229,7 +176,7 @@ const CreateModal: React.FC = (props) => { dirtyRowAction, maxAllowedDirtyRowCount, fullDatabase, - } = jobParameters; + } = parameters; setEnablePartition(!!tables?.find((i) => i?.partitions?.length)); setIsdeleteAfterMigration(deleteAfterMigration); const formData = { @@ -249,11 +196,11 @@ const CreateModal: React.FC = (props) => { archiveRange: fullDatabase ? IArchiveRange.ALL : IArchiveRange.PORTION, triggerStrategy, startAt: undefined, - description, timeoutMillis: milliSecondsToHour(timeoutMillis), syncTableStructure, dirtyRowAction, maxAllowedDirtyRowCount, + scheduleName, }; if (![TaskExecStrategy.START_NOW, TaskExecStrategy.START_AT].includes(triggerStrategy)) { @@ -272,9 +219,9 @@ const CreateModal: React.FC = (props) => { formData.startAt = dayjs(startAt); } await form.setFieldsValue(formData); - setTargetDatabase(jobParameters.targetDatabase); + setTargetDatabase(parameters.targetDatabase); }; - const handleCancel = (hasEdit: boolean) => { + const handleCancel = async (hasEdit: boolean) => { if (hasEdit) { Modal.confirm({ title: formatMessage({ @@ -283,26 +230,51 @@ const CreateModal: React.FC = (props) => { }), //确认取消此 数据归档吗? centered: true, - onOk: () => { - props.modalStore.changeDataArchiveModal(false); + onOk: async () => { + scheduleStore.setDataArchiveData(false); + if (mode === SchedulePageMode.MULTI_PAGE) { + await pageStore?.close?.(pageStore?.activePageKey); + openSchedulesPage(SchedulePageType.DATA_ARCHIVE); + } else { + history.back(); + } }, }); } else { - props.modalStore.changeDataArchiveModal(false); + scheduleStore.setDataArchiveData(false); + if (mode === SchedulePageMode.MULTI_PAGE) { + await pageStore?.close?.(pageStore?.activePageKey); + openSchedulesPage(SchedulePageType.DATA_ARCHIVE); + } else { + history.back(); + } } }; const handleCrontabChange = (crontab) => { setCrontab(crontab); }; - const handleCreate = async (data: Partial) => { - const res = await createTask(data); + const handleCreate = async (data: Partial>) => { + const res = await createSchedule(data); setConfirmLoading(false); - if (res) { + if (res.data) { handleCancel(false); - openTasksPage(TaskPageType.DATA_ARCHIVE, TaskPageScope.CREATED_BY_CURRENT_USER); + message.success('新建成功'); } }; - const handleEditAndConfirm = async (data: Partial) => { + + const handleEdit = async (data: Partial>) => { + data.id = dataArchiveEditId; + const res = await updateSchedule(data); + setConfirmLoading(false); + if (res.data) { + handleCancel(false); + message.success('修改成功'); + } + }; + + const handleEditAndConfirm = async ( + data: Partial>, + ) => { Modal.confirm({ title: formatMessage({ id: 'odc.DataArchiveTask.CreateModal.AreYouSureYouWant.1', @@ -342,7 +314,7 @@ const CreateModal: React.FC = (props) => { //确定 centered: true, onOk: () => { - handleCreate(data); + handleEdit(data); }, onCancel: () => { setConfirmLoading(false); @@ -353,7 +325,7 @@ const CreateModal: React.FC = (props) => { setPreviewModalVisible(false); setPreviewSQL(''); }; - const handleSubmit = () => { + const handleSubmit = (scheduleName?: string) => { form .validateFields() .then(async (values) => { @@ -364,18 +336,14 @@ const CreateModal: React.FC = (props) => { variables, tables: _tables, deleteAfterMigration, - deleteTemporaryTable, triggerStrategy, migrationInsertAction, shardingStrategy, archiveRange, - description, rowLimit, dataSizeLimit, timeoutMillis, syncTableStructure, - dirtyRowAction, - maxAllowedDirtyRowCount, } = values; _tables?.map((i) => { i.partitions = Array.isArray(i.partitions) @@ -385,38 +353,30 @@ const CreateModal: React.FC = (props) => { ?.split(',') ?.filter(Boolean); }); - const parameters = { - type: TaskType.MIGRATION, - operationType: isEdit ? TaskOperationType.UPDATE : TaskOperationType.CREATE, - taskId: dataArchiveEditId, - scheduleTaskParameters: { - sourceDatabaseId: databaseId, - targetDataBaseId, - variables: getVariables(variables), - tables: - archiveRange === IArchiveRange.ALL - ? tables?.map((item) => { - return { - tableName: item?.tableName, - conditionExpression: '', - targetTableName: '', - }; - }) - : _tables, - deleteAfterMigration, - deleteTemporaryTable, - migrationInsertAction, - shardingStrategy, - syncTableStructure, - dirtyRowAction, - maxAllowedDirtyRowCount, - timeoutMillis: hourToMilliSeconds(timeoutMillis), - rateLimit: { - rowLimit, - dataSizeLimit: mbToKb(dataSizeLimit), - }, - fullDatabase: archiveRange === IArchiveRange.ALL, + const parameters: createDataArchiveParameters = { + deleteAfterMigration, + fullDatabase: archiveRange === IArchiveRange.ALL, + migrationInsertAction, + rateLimit: { + rowLimit, + dataSizeLimit: mbToKb(dataSizeLimit), }, + shardingStrategy, + syncTableStructure, + tables: + archiveRange === IArchiveRange.ALL + ? tables?.map((item) => { + return { + tableName: item?.tableName, + conditionExpression: '', + targetTableName: '', + }; + }) + : _tables, + targetDataBaseId, + timeoutMillis: hourToMilliSeconds(timeoutMillis), + variables: getVariables(variables), + sourceDatabaseId: databaseId, triggerConfig: { triggerStrategy, } as ICycleTaskTriggerConfig, @@ -435,16 +395,13 @@ const CreateModal: React.FC = (props) => { startAt: startAt?.valueOf(), }; } - const data = { - databaseId, - taskType: TaskType.ALTER_SCHEDULE, + const data: createScheduleRecord = { + name: scheduleName, + type: ScheduleType.DATA_ARCHIVE, + triggerConfig: parameters.triggerConfig, parameters, - description, }; setConfirmLoading(true); - if (!isEdit) { - delete parameters.taskId; - } if (isEdit) { handleEditAndConfirm(data); } else { @@ -493,9 +450,9 @@ const CreateModal: React.FC = (props) => { }); }; - const handleConfirmTask = () => { + const handleConfirmTask = (scheduleName?: string) => { handleCloseSQLPreviewModal(); - handleSubmit(); + handleSubmit(scheduleName); }; const handleFieldsChange = () => { @@ -505,39 +462,35 @@ const CreateModal: React.FC = (props) => { form?.resetFields(); setCrontab(null); setHasEdit(false); + setTargetDatabase(null); + setCreateScheduleDatabase(undefined); }; - const handleDBChange = () => { + const handleDBChange = (v, db) => { form.setFieldValue('tables', [null]); + setCreateScheduleDatabase(db); }; useEffect(() => { - if (!dataArchiveVisible) { - handleReset(); - setTargetDatabase(null); - } - }, [dataArchiveVisible]); - - useEffect(() => { - if (sourceDB?.id) { - loadTables(); + const databaseId = dataArchiveData?.databaseId; + if (databaseId) { + form.setFieldsValue({ + databaseId, + }); } - }, [sourceDB?.id]); - - useEffect(() => { if (dataArchiveEditId) { loadEditData(dataArchiveEditId); } - }, [dataArchiveEditId]); + return () => { + handleReset(); + }; + }, []); useEffect(() => { - const databaseId = dataArchiveTaskData?.databaseId; - if (databaseId) { - form.setFieldsValue({ - databaseId, - }); + if (sourceDB?.id) { + loadTables(); } - }, [dataArchiveTaskData?.databaseId]); + }, [sourceDB?.id]); /** * 归档到对象存储类型的数据库时,不支持同步结构 @@ -552,57 +505,39 @@ const CreateModal: React.FC = (props) => { }, [targetDatabase]); return ( - - - - - } - open={dataArchiveVisible} - onClose={() => { - handleCancel(hasEdit); +
- - {dataArchiveVisible ? ( + + = (props) => { initialValues={defaultValue} onFieldsChange={handleFieldsChange} > +

+ 基本信息 +

= (props) => { })} /*源端数据库*/ projectId={projectId} onChange={handleDBChange} + onInit={(db) => setCreateScheduleDatabase(db)} filters={{ hideFileSystem: true, }} /> = (props) => { projectId={projectId} /> +

+ 归档范围 +

= (props) => { )} - +

执行方式

+ { @@ -710,7 +646,7 @@ const CreateModal: React.FC = (props) => { {({ getFieldValue }) => { - const triggerStrategy = getFieldValue('triggerStrategy') || []; + const triggerStrategy = getFieldValue('triggerStrategy'); if (triggerStrategy === TaskExecStrategy.START_AT) { return ( = (props) => { return null; }} - +

+ 作业设置 +

+ = (props) => { - - ) : ( - <> - )} -
+
+ handleConfirmTask(scheduleName)} /> - +
+ + + + +
+
); }; -export default inject('modalStore')(observer(CreateModal)); + +export const getVariableValue = ( + value: { + name: string; + pattern: string; + }[], +) => { + var reg = /([+-])?(\d+)?(.+)?/; + return value?.map(({ name, pattern }) => { + const [format, _pattern] = pattern?.split('|'); + let patternValue = { + operator: '', + step: '', + unit: '', + }; + if (_pattern) { + const res = _pattern?.match(reg); + const operator = res[1] ?? ''; + const step = res[2] ?? ''; + const unit = timeUnitOptions.map((item) => item.value).includes(res[3]) ? res[3] : ''; + patternValue = { + operator, + step, + unit, + }; + } + return { + name, + format, + pattern: [patternValue], + }; + }); +}; + +export default inject('scheduleStore', 'pageStore')(observer(Create)); diff --git a/src/component/Task/modals/DataClearTask/DetailContent/ArchiveRange.tsx b/src/component/Schedule/modals/DataClear/Content/ArchiveRange.tsx similarity index 83% rename from src/component/Task/modals/DataClearTask/DetailContent/ArchiveRange.tsx rename to src/component/Schedule/modals/DataClear/Content/ArchiveRange.tsx index 8bf5e2e8c..38925bb71 100644 --- a/src/component/Task/modals/DataClearTask/DetailContent/ArchiveRange.tsx +++ b/src/component/Schedule/modals/DataClear/Content/ArchiveRange.tsx @@ -1,19 +1,3 @@ -/* - * 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 DisplayTable from '@/component/DisplayTable'; import { formatMessage } from '@/util/intl'; import { Flex, Popover, Tooltip, Typography } from 'antd'; diff --git a/src/component/Schedule/modals/DataClear/Content/index.tsx b/src/component/Schedule/modals/DataClear/Content/index.tsx new file mode 100644 index 000000000..e7f55cf90 --- /dev/null +++ b/src/component/Schedule/modals/DataClear/Content/index.tsx @@ -0,0 +1,282 @@ +import { IScheduleRecord, IDataClearParameters } from '@/d.ts/schedule'; +import RiskLevelLabel from '@/component/RiskLevelLabel'; +import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; +import { formatMessage } from '@/util/intl'; +import { getFormatDateTime, milliSecondsToHour } from '@/util/utils'; +import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; +import VariableConfigTable from '@/component/Task/component/VariableConfigTable'; +import ArchiveRange from './ArchiveRange'; +import { getScheduleExecStrategyMap } from '@/component/Schedule/helper'; +import { kbToMb, mbToKb } from '@/util/utils'; +import { Descriptions, Divider, message } from 'antd'; +import ThrottleEditableCell from '@/component/Task/component/ThrottleEditableCell'; +import { + DirtyRowActionEnum, + DirtyRowActionLabelMap, +} from '@/component/ExecuteSqlDetailModal/constant'; +import setting from '@/store/setting'; +import { shardingStrategyOptions } from '@/component/Task/component/ShardingStrategyItem'; +import { updateLimiterConfig } from '@/common/network/schedule'; +import { IScheduleTaskRecord, scheduleTask } from '@/d.ts/scheduleTask'; +import { SubTypeTextMap } from '@/constant/scheduleTask'; +import EllipsisText from '@/component/EllipsisText'; + +interface IProps { + schedule: IScheduleRecord; + subTask?: scheduleTask; + onReload?: () => void; +} +const DataClearScheduleContent: React.FC = (props) => { + const { schedule, onReload, subTask } = props; + const { parameters } = schedule || {}; + + const handleRowLimit = async (rowLimit, handleClose) => { + const res = await updateLimiterConfig(schedule?.scheduleId, { + rowLimit, + }); + if (res) { + message.success( + formatMessage({ + id: 'odc.src.component.Task.DataArchiveTask.DetailContent.SuccessfullyModified', + defaultMessage: '修改成功!', + }), //'修改成功!' + ); + handleClose(); + onReload?.(); + } + }; + + const handleDataSizeLimit = async (dataSizeLimit, handleClose) => { + const res = await updateLimiterConfig(schedule?.scheduleId, { + dataSizeLimit: mbToKb(dataSizeLimit), + }); + if (res) { + message.success( + formatMessage({ + id: 'odc.src.component.Task.DataArchiveTask.DetailContent.SuccessfullyModified.1', + defaultMessage: '修改成功!', + }), //'修改成功!' + ); + handleClose(); + onReload(); + } + }; + + return ( + <> + + {subTask && ( + <> + {subTask?.id} + {SubTypeTextMap[subTask?.type]} + + )} + {!subTask && ( + <> + {schedule?.scheduleId} + 数据清理 + + )} + + +
+ +
+ } + /> +
+
+
+ + + + + {parameters?.needCheckBeforeDelete ? '是' : '否'} + + + + + {parameters?.targetDatabase && ( + <> + + + + + + {parameters?.targetDatabase?.dataSource?.name} + + + )} + + + +
+ + + + + } + direction="column" + /> + + + + } + direction="column" + /> + + {parameters?.needCheckBeforeDelete ? ( + + {DirtyRowActionLabelMap[parameters?.dirtyRowAction]} + + ) : null} + {parameters?.dirtyRowAction === DirtyRowActionEnum.SKIP ? ( + + {formatMessage( + { + id: 'src.component.Task.DataClearTask.DetailContent.66E3D51C', + defaultMessage: '{LogicalExpression0} 行', + }, + { LogicalExpression0: parameters?.maxAllowedDirtyRowCount || 0 }, + )} + + ) : null} + + {shardingStrategyOptions.find((item) => item.value === parameters?.shardingStrategy) + ?.label || '-'} + + + + + + + + + {parameters?.deleteByUniqueKey + ? formatMessage({ + id: 'src.component.Task.DataClearTask.DetailContent.D2882643', + defaultMessage: '是', + }) + : formatMessage({ + id: 'src.component.Task.DataClearTask.DetailContent.834E7D89', + defaultMessage: '否', + })} + + + {parameters?.timeoutMillis ? milliSecondsToHour(parameters?.timeoutMillis) + 'h' : '-'} + + + + + + {schedule?.creator?.name || '-'} + + + {getFormatDateTime(schedule?.createTime)} + + + + ); +}; + +export default DataClearScheduleContent; diff --git a/src/component/Task/modals/DataClearTask/CreateModal/ArchiveRange.tsx b/src/component/Schedule/modals/DataClear/Create/ArchiveRange.tsx similarity index 99% rename from src/component/Task/modals/DataClearTask/CreateModal/ArchiveRange.tsx rename to src/component/Schedule/modals/DataClear/Create/ArchiveRange.tsx index e8d25ec92..143ef9aaf 100644 --- a/src/component/Task/modals/DataClearTask/CreateModal/ArchiveRange.tsx +++ b/src/component/Schedule/modals/DataClear/Create/ArchiveRange.tsx @@ -20,7 +20,7 @@ import { PlusOutlined, SettingOutlined, SettingFilled } from '@ant-design/icons' import { Tooltip, Button, Checkbox, Form, Input, Radio, Select, Typography } from 'antd'; import classNames from 'classnames'; import { useEffect, useState } from 'react'; -import ArchiveRangeTip from '@/component/Task/component/ArchiveRangeTip'; +import ArchiveRangeTip from '@/component/Schedule/components/ArchiveRangeTip'; import { PartitionTextArea } from '@/component/Task/component/PartitionTextArea'; import { IArchiveRange } from './index'; import BatchSelectionPopover from '@/component/BatchSelectionPopover'; diff --git a/src/component/Task/modals/DataClearTask/CreateModal/VariableConfig.tsx b/src/component/Schedule/modals/DataClear/Create/VariableConfig.tsx similarity index 98% rename from src/component/Task/modals/DataClearTask/CreateModal/VariableConfig.tsx rename to src/component/Schedule/modals/DataClear/Create/VariableConfig.tsx index f604ca52a..41d9bc64a 100644 --- a/src/component/Task/modals/DataClearTask/CreateModal/VariableConfig.tsx +++ b/src/component/Schedule/modals/DataClear/Create/VariableConfig.tsx @@ -20,7 +20,7 @@ import { DeleteOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons'; import type { FormInstance } from 'antd'; import { Button, Form, Input, InputNumber, Select, Space } from 'antd'; import classNames from 'classnames'; -import { timeUnitOptions } from '@/component/Task/modals/DataArchiveTask/CreateModal/VariableConfig'; +import { timeUnitOptions } from '@/component/Schedule/modals/DataArchive/Create/VariableConfig'; import { variable } from './index'; import styles from './index.less'; import { rules } from './const'; diff --git a/src/component/Task/modals/DataClearTask/CreateModal/const.ts b/src/component/Schedule/modals/DataClear/Create/const.ts similarity index 100% rename from src/component/Task/modals/DataClearTask/CreateModal/const.ts rename to src/component/Schedule/modals/DataClear/Create/const.ts diff --git a/src/component/Task/modals/DataClearTask/CreateModal/index.less b/src/component/Schedule/modals/DataClear/Create/index.less similarity index 99% rename from src/component/Task/modals/DataClearTask/CreateModal/index.less rename to src/component/Schedule/modals/DataClear/Create/index.less index 520770fcf..31328c695 100644 --- a/src/component/Task/modals/DataClearTask/CreateModal/index.less +++ b/src/component/Schedule/modals/DataClear/Create/index.less @@ -1,4 +1,4 @@ -.data-archive { +.dataArchive { .variables { display: grid; grid-gap: 8px; diff --git a/src/component/Task/modals/DataClearTask/CreateModal/index.tsx b/src/component/Schedule/modals/DataClear/Create/index.tsx similarity index 73% rename from src/component/Task/modals/DataClearTask/CreateModal/index.tsx rename to src/component/Schedule/modals/DataClear/Create/index.tsx index 8e597869b..383a17bdc 100644 --- a/src/component/Task/modals/DataClearTask/CreateModal/index.tsx +++ b/src/component/Schedule/modals/DataClear/Create/index.tsx @@ -1,55 +1,35 @@ -/* - * 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 { getTableListByDatabaseName } from '@/common/network/table'; -import { createTask, getCycleTaskDetail, previewSqlStatements } from '@/common/network/task'; +import { previewSqlStatements } from '@/common/network/task'; +import { createSchedule, updateSchedule, getScheduleDetail } from '@/common/network/schedule'; import Crontab from '@/component/Crontab'; import { CrontabDateType, CrontabMode, ICrontab } from '@/component/Crontab/interface'; import FormItemPanel from '@/component/FormItemPanel'; import DescriptionInput from '@/component/Task/component/DescriptionInput'; +import { ICycleTaskTriggerConfig, ITable, TaskExecStrategy, ShardingStrategy } from '@/d.ts'; +import { history } from '@umijs/max'; import { - CreateTaskRecord, - ICycleTaskTriggerConfig, - IDataClearJobParameters, - ITable, - TaskExecStrategy, - TaskJobType, - TaskOperationType, - TaskPageScope, - TaskPageType, - TaskType, - ShardingStrategy, - CycleTaskDetail, -} from '@/d.ts'; -import { openTasksPage } from '@/store/helper/page'; -import type { ModalStore } from '@/store/modal'; + IDataClearParameters, + IScheduleRecord, + ScheduleType, + createScheduleRecord, + createDataDeleteParameters, + SchedulePageType, +} from '@/d.ts/schedule'; import { useDBSession } from '@/store/sessionManager/hooks'; import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; import { hourToMilliSeconds, kbToMb, mbToKb, milliSecondsToHour } from '@/util/utils'; import { FieldTimeOutlined } from '@ant-design/icons'; -import { Button, Checkbox, DatePicker, Drawer, Form, Modal, Radio, Space, Spin } from 'antd'; +import { Button, Checkbox, DatePicker, Form, Modal, Radio, Space, Spin, message } from 'antd'; import { inject, observer } from 'mobx-react'; +import { CreateScheduleContext } from '@/component/Schedule/context/createScheduleContext'; import dayjs from 'dayjs'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import DatabaseSelect from '@/component/Task/component/DatabaseSelect'; import SQLPreviewModal from '@/component/Task/component/SQLPreviewModal'; import TaskdurationItem from '@/component/Task/component/TaskdurationItem'; import ThrottleFormItem from '@/component/Task/component/ThrottleFormItem'; -import { getVariableValue } from '@/component/Task/modals/DataArchiveTask/CreateModal'; +import { getVariableValue } from '@/component/Schedule/modals/DataArchive/Create'; import ArchiveRange from './ArchiveRange'; import styles from './index.less'; import VariableConfig from './VariableConfig'; @@ -59,6 +39,11 @@ import { useRequest } from 'ahooks'; import DirtyRowAction from '@/component/Task/component/DirtyRowAction'; import MaxAllowedDirtyRowCount from '@/component/Task/component/MaxAllowedDirtyRowCount'; import { rules } from './const'; +import AnchorContainer from '@/component/AnchorContainer'; +import { ScheduleStore } from '@/store/schedule'; +import { PageStore } from '@/store/page'; +import { SchedulePageMode } from '@/component/Schedule/interface'; +import { openSchedulesPage } from '@/store/helper/page'; export enum IArchiveRange { PORTION = 'portion', @@ -96,10 +81,6 @@ const defaultValue = { dataSizeLimit: 1, deleteByUniqueKey: true, }; -interface IProps { - modalStore?: ModalStore; - projectId?: number; -} const getVariables = ( value: { name: string; @@ -126,9 +107,16 @@ const getVariables = ( }; }); }; -const CreateModal: React.FC = (props) => { - const { modalStore, projectId } = props; - const { dataClearVisible, dataClearTaskData } = modalStore; + +interface IProps { + scheduleStore?: ScheduleStore; + pageStore?: PageStore; + projectId?: number; + mode?: SchedulePageMode; +} + +const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) => { + const { dataClearData } = scheduleStore; const [previewModalVisible, setPreviewModalVisible] = useState(false); const [previewSql, setPreviewSQL] = useState(''); const [hasEdit, setHasEdit] = useState(false); @@ -144,24 +132,25 @@ const CreateModal: React.FC = (props) => { resetFields: () => void; }>(); const databaseName = database?.name; - const editTaskId = dataClearTaskData?.id; - const isEdit = !!editTaskId && dataClearTaskData?.type === 'EDIT'; - + const editScheduleId = dataClearData?.id; + const isEdit = !!editScheduleId && dataClearData?.type === 'EDIT'; const loadTables = async () => { const tables = await getTableListByDatabaseName(session?.sessionId, databaseName); setTables(tables); }; - const { run: fetchCycleTaskDetail, loading } = useRequest(getCycleTaskDetail, { manual: true }); + const { run: fetchScheduleDetail, loading } = useRequest(getScheduleDetail, { manual: true }); + const { createScheduleDatabase, setCreateScheduleDatabase } = + useContext(CreateScheduleContext) || {}; const loadEditData = async (editId: number) => { - const data = (await fetchCycleTaskDetail(editId)) as CycleTaskDetail; - + const dataRes = (await fetchScheduleDetail(editId)) as IScheduleRecord; + setCreateScheduleDatabase(dataRes?.parameters?.database); const { - jobParameters, - description, + parameters, + scheduleName, triggerConfig: { triggerStrategy, cronExpression, hours, days, startAt }, - } = data; + } = dataRes; const { databaseId, rateLimit, @@ -175,7 +164,7 @@ const CreateModal: React.FC = (props) => { dirtyRowAction, maxAllowedDirtyRowCount, fullDatabase, - } = jobParameters; + } = parameters; setEnablePartition(!!tables?.find((i) => i?.partitions?.length)); const formData = { databaseId, @@ -191,12 +180,12 @@ const CreateModal: React.FC = (props) => { archiveRange: fullDatabase ? IArchiveRange.ALL : IArchiveRange.PORTION, triggerStrategy, startAt: undefined, - description, needCheckBeforeDelete, targetDatabaseId, timeoutMillis: milliSecondsToHour(timeoutMillis), dirtyRowAction, maxAllowedDirtyRowCount, + scheduleName, }; if (![TaskExecStrategy.START_NOW, TaskExecStrategy.START_AT].includes(triggerStrategy)) { @@ -216,7 +205,8 @@ const CreateModal: React.FC = (props) => { } form.setFieldsValue(formData); }; - const handleCancel = (hasEdit: boolean) => { + + const handleCancel = async (hasEdit: boolean) => { if (hasEdit) { Modal.confirm({ title: formatMessage({ @@ -225,26 +215,55 @@ const CreateModal: React.FC = (props) => { }), //确认取消此数据清理吗? centered: true, - onOk: () => { - props.modalStore.changeDataClearModal(false); + onOk: async () => { + scheduleStore.setDataClearData(false); + setCreateScheduleDatabase(undefined); + if (mode === SchedulePageMode.MULTI_PAGE) { + await pageStore?.close?.(pageStore.activePageKey); + openSchedulesPage(SchedulePageType.DATA_DELETE); + } else { + history.back(); + } }, }); } else { - props.modalStore.changeDataClearModal(false); + scheduleStore.setDataClearData(false); + setCreateScheduleDatabase(undefined); + if (mode === SchedulePageMode.MULTI_PAGE) { + await pageStore?.close?.(pageStore.activePageKey); + openSchedulesPage(SchedulePageType.DATA_DELETE); + } else { + history.back(); + } } }; + const handleCrontabChange = (crontab) => { setCrontab(crontab); }; - const handleCreate = async (data: Partial) => { - const res = await createTask(data); + + const handleCreate = async (data: Partial>) => { + const res = await createSchedule(data); + setConfirmLoading(false); + if (res?.data) { + handleCancel(false); + message.success('新建成功'); + } + }; + + const handleEdit = async (data: Partial>) => { + data.id = editScheduleId; + const res = await updateSchedule(data); setConfirmLoading(false); - if (res) { + if (res?.data) { handleCancel(false); - openTasksPage(TaskPageType.DATA_DELETE, TaskPageScope.CREATED_BY_CURRENT_USER); + message.success('修改成功'); } }; - const handleEditAndConfirm = async (data: Partial) => { + + const handleEditAndConfirm = async ( + data: Partial>, + ) => { Modal.confirm({ title: formatMessage({ id: 'odc.DataClearTask.CreateModal.AreYouSureYouWant.1', @@ -284,19 +303,20 @@ const CreateModal: React.FC = (props) => { //确定 centered: true, onOk: () => { - handleCreate(data); + handleEdit(data); }, onCancel: () => { setConfirmLoading(false); }, }); }; + const handleCloseSQLPreviewModal = () => { setPreviewModalVisible(false); setPreviewSQL(''); }; - const handleSubmit = () => { + const handleSubmit = (scheduleName?: string) => { form .validateFields() .then(async (values) => { @@ -307,7 +327,6 @@ const CreateModal: React.FC = (props) => { tables: _tables, triggerStrategy, archiveRange, - description, shardingStrategy, rowLimit, dataSizeLimit, @@ -315,8 +334,6 @@ const CreateModal: React.FC = (props) => { timeoutMillis, needCheckBeforeDelete, targetDatabaseId, - dirtyRowAction, - maxAllowedDirtyRowCount, } = values; _tables?.map((i) => { i.partitions = Array.isArray(i.partitions) @@ -326,36 +343,29 @@ const CreateModal: React.FC = (props) => { ?.split(',') ?.filter(Boolean); }); - const parameters = { - type: TaskJobType.DATA_DELETE, - operationType: isEdit ? TaskOperationType.UPDATE : TaskOperationType.CREATE, - taskId: editTaskId, - scheduleTaskParameters: { - fullDatabase: archiveRange === IArchiveRange.ALL, - databaseId, - deleteByUniqueKey, - variables: getVariables(variables), - shardingStrategy, - tables: - archiveRange === IArchiveRange.ALL - ? tables?.map((item) => { - return { - tableName: item?.tableName, - conditionExpression: '', - targetTableName: '', - }; - }) - : _tables, - timeoutMillis: hourToMilliSeconds(timeoutMillis), - rateLimit: { - rowLimit, - dataSizeLimit: mbToKb(dataSizeLimit), - }, - needCheckBeforeDelete, - targetDatabaseId: targetDatabaseId, - dirtyRowAction, - maxAllowedDirtyRowCount, + const parameters: createDataDeleteParameters = { + databaseId, + deleteByUniqueKey, + fullDatabase: archiveRange === IArchiveRange.ALL, + needCheckBeforeDelete, + rateLimit: { + rowLimit, + dataSizeLimit: mbToKb(dataSizeLimit), }, + variables: getVariables(variables), + shardingStrategy, + targetDatabaseId, + tables: + archiveRange === IArchiveRange.ALL + ? tables?.map((item) => { + return { + tableName: item?.tableName, + conditionExpression: '', + targetTableName: '', + }; + }) + : _tables, + timeoutMillis: hourToMilliSeconds(timeoutMillis), triggerConfig: { triggerStrategy, } as ICycleTaskTriggerConfig, @@ -374,16 +384,13 @@ const CreateModal: React.FC = (props) => { startAt: startAt?.valueOf(), }; } - const data = { - databaseId, - taskType: TaskType.ALTER_SCHEDULE, + const data: createScheduleRecord = { + name: scheduleName, + type: ScheduleType.DATA_DELETE, parameters, - description, + triggerConfig: parameters.triggerConfig, }; setConfirmLoading(true); - if (!isEdit) { - delete parameters.taskId; - } if (isEdit) { handleEditAndConfirm(data); } else { @@ -395,7 +402,6 @@ const CreateModal: React.FC = (props) => { console.error(JSON.stringify(errorInfo)); }); }; - const handleSQLPreview = () => { form .validateFields() @@ -433,9 +439,9 @@ const CreateModal: React.FC = (props) => { }); }; - const handleConfirmTask = () => { + const handleConfirmTask = (scheduleName?: string) => { handleCloseSQLPreviewModal(); - handleSubmit(); + handleSubmit(scheduleName); }; const handleFieldsChange = () => { @@ -445,90 +451,69 @@ const CreateModal: React.FC = (props) => { form?.resetFields(); setCrontab(null); setHasEdit(false); + setCreateScheduleDatabase(undefined); }; - const handleDBChange = () => { + const handleDBChange = (v, db) => { form.setFieldValue('tables', [null]); + setCreateScheduleDatabase(db); }; useEffect(() => { - if (!dataClearVisible) { - handleReset(); - } - }, [dataClearVisible]); - useEffect(() => { - if (database?.id) { - loadTables(); + if (editScheduleId) { + loadEditData(editScheduleId); } - }, [database?.id]); - - useEffect(() => { - if (editTaskId) { - loadEditData(editTaskId); - } - }, [editTaskId]); - - useEffect(() => { - const databaseId = dataClearTaskData?.databaseId; + const databaseId = dataClearData?.databaseId; if (databaseId) { form.setFieldsValue({ databaseId, }); } - }, [dataClearTaskData?.databaseId]); + return () => { + handleReset(); + }; + }, []); + + useEffect(() => { + if (database?.id) { + loadTables(); + } + }, [database?.id]); return ( - - - - - } - open={dataClearVisible} - onClose={() => { - handleCancel(hasEdit); +
- - {dataClearVisible ? ( + +
= (props) => { initialValues={defaultValue} onFieldsChange={handleFieldsChange} > - {/* */} +

+ 基本信息 +

setCreateScheduleDatabase(db)} /> - - {/*
*/} +

+ 清理范围 +

{({ getFieldValue }) => { @@ -564,14 +553,10 @@ const CreateModal: React.FC = (props) => { - +

+ 执行方式 +

+ { @@ -630,15 +615,10 @@ const CreateModal: React.FC = (props) => { return null; }} - +

+ 作业设置 +

+ {formatMessage({ @@ -653,7 +633,7 @@ const CreateModal: React.FC = (props) => { return ( needCheckBeforeDelete && ( = (props) => { - - ) : ( - <> - )} -
+
+ handleConfirmTask(scheduleName)} /> - +
+ + + + +
+
); }; -export default inject('modalStore')(observer(CreateModal)); + +export default inject('scheduleStore', 'pageStore')(observer(Create)); diff --git a/src/component/Schedule/modals/Detail.tsx b/src/component/Schedule/modals/Detail.tsx new file mode 100644 index 000000000..26cb41bab --- /dev/null +++ b/src/component/Schedule/modals/Detail.tsx @@ -0,0 +1,11 @@ +import DataClearScheduleContent from './DataClear/Content/index'; +import PartitionScheduleContent from './PartitionPlan/Content/index'; +import DataArchiveScheduleContent from './DataArchive/Content/index'; +import SQLPlanScheduleContent from './SQLPlan/Content/index'; + +export { + DataClearScheduleContent, + PartitionScheduleContent, + DataArchiveScheduleContent, + SQLPlanScheduleContent, +}; diff --git a/src/component/Task/modals/PartitionTask/DetailContent/CycleDescriptionItem.tsx b/src/component/Schedule/modals/PartitionPlan/Content/CycleDescriptionItem.tsx similarity index 77% rename from src/component/Task/modals/PartitionTask/DetailContent/CycleDescriptionItem.tsx rename to src/component/Schedule/modals/PartitionPlan/Content/CycleDescriptionItem.tsx index fe444351b..9cd605ea7 100644 --- a/src/component/Task/modals/PartitionTask/DetailContent/CycleDescriptionItem.tsx +++ b/src/component/Schedule/modals/PartitionPlan/Content/CycleDescriptionItem.tsx @@ -1,20 +1,4 @@ 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 { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; import { getCronCycle } from '@/component/Task/component/TaskTable/utils'; import type { ICycleTaskTriggerConfig } from '@/d.ts'; diff --git a/src/component/Task/modals/PartitionTask/DetailContent/index.less b/src/component/Schedule/modals/PartitionPlan/Content/index.less similarity index 100% rename from src/component/Task/modals/PartitionTask/DetailContent/index.less rename to src/component/Schedule/modals/PartitionPlan/Content/index.less diff --git a/src/component/Schedule/modals/PartitionPlan/Content/index.tsx b/src/component/Schedule/modals/PartitionPlan/Content/index.tsx new file mode 100644 index 000000000..a2dc285b3 --- /dev/null +++ b/src/component/Schedule/modals/PartitionPlan/Content/index.tsx @@ -0,0 +1,135 @@ +import { + ScheduleType, + SchedulePageType, + IScheduleRecord, + ScheduleRecordParameters, + ScheduleStatus, + IPartitionPlan, +} from '@/d.ts/schedule'; +import { Descriptions, Divider, Typography } from 'antd'; +import { formatMessage } from '@/util/intl'; +import PartitionPolicyTable from '@/component/Task/component/PartitionPolicyTable'; +import CycleDescriptionItem from './CycleDescriptionItem'; +import { ErrorStrategyMap } from '@/component/Task/const'; +import { getFormatDateTime, milliSecondsToHour } from '@/util/utils'; +import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; +import RiskLevelLabel from '@/component/RiskLevelLabel'; +import { IScheduleTaskRecord, scheduleTask } from '@/d.ts/scheduleTask'; +import { SubTypeTextMap } from '@/constant/scheduleTask'; +import EllipsisText from '@/component/EllipsisText'; + +interface IProps { + schedule: IScheduleRecord; + subTask?: scheduleTask; +} +const PartitionScheduleContent: React.FC = (props) => { + const { schedule, subTask } = props; + const { parameters } = schedule || {}; + + const executionTimeout = milliSecondsToHour(parameters?.timeoutMillis); + + return ( + <> + + {subTask && ( + <> + {subTask?.id} + {SubTypeTextMap[subTask?.type]} + + )} + {!subTask && ( + <> + {schedule?.scheduleId} + + {formatMessage({ + id: 'odc.src.component.Task.PartitionTask.DetailContent.Partition', + defaultMessage: '分区计划', + })} + + + )} + +
+ +
+ } + /> +
+
+
+ + + + + + +
+ + + + + + + {ErrorStrategyMap[parameters?.errorStrategy]} + + + {executionTimeout || '-'} + {formatMessage({ + id: 'src.component.Task.PartitionTask.DetailContent.B08D0E80' /*小时*/, + defaultMessage: '小时', + })} + + + + + + + {schedule?.creator?.name || '-'} + + + {getFormatDateTime(schedule?.createTime)} + + + + ); +}; + +export default PartitionScheduleContent; diff --git a/src/component/Task/modals/PartitionTask/CreateModal/const.ts b/src/component/Schedule/modals/PartitionPlan/Create/const.ts similarity index 100% rename from src/component/Task/modals/PartitionTask/CreateModal/const.ts rename to src/component/Schedule/modals/PartitionPlan/Create/const.ts diff --git a/src/component/Task/modals/PartitionTask/CreateModal/index.less b/src/component/Schedule/modals/PartitionPlan/Create/index.less similarity index 100% rename from src/component/Task/modals/PartitionTask/CreateModal/index.less rename to src/component/Schedule/modals/PartitionPlan/Create/index.less diff --git a/src/component/Schedule/modals/PartitionPlan/Create/index.tsx b/src/component/Schedule/modals/PartitionPlan/Create/index.tsx new file mode 100644 index 000000000..9336e51aa --- /dev/null +++ b/src/component/Schedule/modals/PartitionPlan/Create/index.tsx @@ -0,0 +1,1010 @@ +import { getPartitionPlanTables } from '@/common/network/task'; +import { createSchedule, updateSchedule, getScheduleDetail } from '@/common/network/schedule'; +import Crontab from '@/component/Crontab'; +import { CrontabDateType, CrontabMode, ICrontab } from '@/component/Crontab/interface'; +import FormItemPanel from '@/component/FormItemPanel'; +import { + IPartitionPlanKeyType, + IPartitionTableConfig, + PARTITION_KEY_INVOKER, + PARTITION_NAME_INVOKER, + TaskErrorStrategy, + TaskExecStrategy, + TaskPartitionStrategy, +} from '@/d.ts'; +import { history } from '@umijs/max'; +import { useDBSession } from '@/store/sessionManager/hooks'; +import { formatMessage } from '@/util/intl'; +import { hourToMilliSeconds, milliSecondsToHour } from '@/util/utils'; +import { + Alert, + Button, + Checkbox, + Divider, + Form, + Input, + InputNumber, + Modal, + Radio, + Space, + Spin, + Tooltip, + Typography, + message, + DatePicker, +} from 'antd'; +import { inject, observer } from 'mobx-react'; +import dayjs from 'dayjs'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import DatabaseSelect from '@/component/Task/component/DatabaseSelect'; +import PartitionPolicyFormTable from '@/component/Task/component/PartitionPolicyFormTable'; +import { + getPartitionKeyInvokerByIncrementFieldType, + INCREAMENT_FIELD_TYPE, + START_DATE, +} from '@/component/Task/component/PartitionPolicyFormTable/const'; +import styles from './index.less'; +import { useRequest } from 'ahooks'; +import { rules } from './const'; +import { Rule } from 'antd/es/form'; +import AnchorContainer from '@/component/AnchorContainer'; +import CreateTaskConfirmModal from '@/component/Task/component/CreateTaskConfirmModal'; +import { ScheduleStore } from '@/store/schedule'; +import { + IScheduleRecord, + IPartitionPlan, + ScheduleType, + createScheduleRecord, + createPartitionPlanParameters, + SchedulePageType, +} from '@/d.ts/schedule'; +import { FieldTimeOutlined } from '@ant-design/icons'; +import { disabledDate, disabledTime } from '@/util/utils'; +import { CreateScheduleContext } from '@/component/Schedule/context/createScheduleContext'; +import { PageStore } from '@/store/page'; +import { SchedulePageMode } from '@/component/Schedule/interface'; +import { openSchedulesPage } from '@/store/helper/page'; + +const { Paragraph, Text } = Typography; + +const historyPartitionKeyInvokers = [ + PARTITION_KEY_INVOKER.HISTORICAL_PARTITION_PLAN_CREATE_GENERATOR, + PARTITION_KEY_INVOKER.HISTORICAL_PARTITION_PLAN_DROP_GENERATOR, +]; + +const validPartitionKeyInvokers = [ + PARTITION_KEY_INVOKER.CUSTOM_GENERATOR, + PARTITION_KEY_INVOKER.TIME_INCREASING_GENERATOR, + PARTITION_KEY_INVOKER.KEEP_MOST_LATEST_GENERATOR, + PARTITION_KEY_INVOKER.TIME_STRING_INCREASING_GENERATOR, + PARTITION_KEY_INVOKER.NUMBER_INCREASING_GENERATOR, +]; + +export enum IPartitionPlanInspectTriggerStrategy { + EVERY_DAY = 'EVERY_DAY', + FIRST_DAY_OF_MONTH = 'FIRST_DAY_OF_MONTH', + LAST_DAY_OF_MONTH = 'LAST_DAY_OF_MONTH', + NONE = 'NONE', +} + +export interface ITableConfig { + __id: number; + __isCreate?: boolean; + containsCreateStrategy: boolean; + containsDropStrategy: boolean; + tableName: string; + definitionCount?: number; + generateCount?: number; + nameRuleType?: string; + generateExpr?: string; + keepLatestCount?: number; + reloadIndexes?: boolean; + namingPrefix?: string; + namingSuffixExpression?: string; + namingSuffixStrategy?: string; + refPartitionKey?: string; + intervalGenerateExpr?: string; + strategies?: TaskPartitionStrategy[]; + partitionMode?: string; + option: { + partitionKeyConfigs: { + name: string; + type?: IPartitionPlanKeyType; + partitionKeyInvoker?: PARTITION_KEY_INVOKER; + fromCurrentTime?: START_DATE; + baseTimestampMillis?: number; + generateExpr?: string; + interval?: string; + intervalPrecision?: number; + intervalGenerateExpr?: string; + incrementFieldType?: INCREAMENT_FIELD_TYPE; + incrementFieldTypeInDate?: string; + fieldType?: INCREAMENT_FIELD_TYPE; + timeFormat?: string; + numberInterval?: string; + }[]; + }; +} + +const getCreatedTableConfigs: (tableConfigs: IPartitionTableConfig[]) => ITableConfig[] = ( + tableConfigs, +) => { + const originPartitionTableConfigs = tableConfigs?.map((config) => { + const dropKeyConfig = config?.partitionKeyConfigs?.find( + (item) => item?.strategy === TaskPartitionStrategy.DROP, + ); + const createKeyConfigs = config?.partitionKeyConfigs?.filter( + (item) => item?.strategy === TaskPartitionStrategy.CREATE, + ); + const keyConfigs = createKeyConfigs?.map((keyConfig) => { + const { partitionKey, partitionKeyInvoker, partitionKeyInvokerParameters } = keyConfig; + const { generateParameter, generateCount } = partitionKeyInvokerParameters ?? {}; + return { + name: partitionKey, + partitionKeyInvoker, + generateCount, + ...generateParameter, + fromCurrentTime: generateParameter?.fromCurrentTime + ? START_DATE.CURRENT_DATE + : START_DATE.CUSTOM_DATE, + baseTimestampMillis: generateParameter?.baseTimestampMillis + ? dayjs(generateParameter?.baseTimestampMillis) + : undefined, + }; + }); + const dropPartitionKeyInvokerParameters = dropKeyConfig?.partitionKeyInvokerParameters ?? {}; + const { partitionNameGeneratorConfig } = config?.partitionNameInvokerParameters ?? {}; + const tableConfig = { + ...dropPartitionKeyInvokerParameters, + ...partitionNameGeneratorConfig, + tableName: config?.tableName, + generateCount: keyConfigs?.[0]?.generateCount, + option: { + partitionKeyConfigs: keyConfigs, + }, + }; + return tableConfig; + }); + return originPartitionTableConfigs; +}; + +interface IProps { + projectId?: number; + scheduleStore?: ScheduleStore; + pageStore?: PageStore; + mode?: SchedulePageMode; +} + +const Create: React.FC = ({ projectId, scheduleStore, pageStore, mode }) => { + const { partitionPlanData } = scheduleStore; + const [tableConfigs, setTableConfigs] = useState(); + const [confirmLoading, setConfirmLoading] = useState(false); + const [disabledSubmit, setDisabledSubmit] = useState(true); + const [hasPartitionPlan, setHasPartitionPlan] = useState(false); + const [crontab, setCrontab] = useState(null); + const [dropCrontab, setDropCrontab] = useState(null); + const [createdTableConfigs, setCreatedTableConfigs] = useState([]); + const [createdOriginTableConfigs, setCreatedOriginTableConfigs] = useState< + IPartitionTableConfig[] + >([]); + const [historyOriginTableConfigs, setHistoryOriginTableConfigs] = useState< + IPartitionTableConfig[] + >([]); + const [form] = Form.useForm(); + const isCustomStrategy = Form.useWatch('isCustomStrategy', form); + const triggerStrategy = Form.useWatch('triggerStrategy', form); + const databaseId = Form.useWatch('databaseId', form); + const { session } = useDBSession(databaseId); + const sessionId = session?.sessionId; + const crontabRef = useRef<{ + setValue: (value: ICrontab) => void; + resetFields: () => void; + }>(); + const isEdit = partitionPlanData?.type === 'EDIT'; + const [open, setOpen] = useState(false); + const crontabDropRef = useRef<{ + setValue: (value: ICrontab) => void; + resetFields: () => void; + }>(); + const [preTableConfigs, setPreTableConfigs] = useState([]); + const { createScheduleDatabase, setCreateScheduleDatabase } = + useContext(CreateScheduleContext) || {}; + + const { run: fetchPartitionPlanTables, loading: fetchPartitionPlanTablesLoading } = useRequest( + getPartitionPlanTables, + { + manual: true, + }, + ); + + const loadData = async () => { + if (sessionId && databaseId) { + const res = await fetchPartitionPlanTables(sessionId, databaseId); + const hasPartitionPlan = res?.contents?.some( + (item) => item?.containsCreateStrategy || item?.containsDropStrategy, + ); + const allPartitionPlanTableConfigs = res?.contents + ?.map((item) => item?.partitionPlanTableConfig) + ?.filter(Boolean); + const createdOriginTableConfigs = allPartitionPlanTableConfigs?.filter( + ({ partitionKeyConfigs }) => { + return partitionKeyConfigs?.some( + (item) => + validPartitionKeyInvokers.includes(item?.partitionKeyInvoker) || + historyPartitionKeyInvokers.includes(item?.partitionKeyInvoker), + ); + }, + ); + const historyOriginTableConfigs = allPartitionPlanTableConfigs?.filter( + ({ partitionKeyConfigs }) => { + return partitionKeyConfigs?.some((item) => + historyPartitionKeyInvokers.includes(item?.partitionKeyInvoker), + ); + }, + ); + isEdit && setCreatedTableConfigs(getCreatedTableConfigs(preTableConfigs)); + setCreatedOriginTableConfigs(createdOriginTableConfigs); + setHistoryOriginTableConfigs(historyOriginTableConfigs); + setHasPartitionPlan(hasPartitionPlan); + const tableConfigs = res?.contents + ?.map((config, index) => { + const strategies = []; + const { + containsCreateStrategy, + containsDropStrategy, + name: tableName, + partition, + partitionMode, + } = config; + if (isEdit) { + const prevConfigs = preTableConfigs?.find((item) => item.tableName === tableName); + if (!prevConfigs) { + return false; + } + const { + containsCreateStrategy: prevContainsCreateStrategy, + containsDropStrategy: prevContainsDropStrategy, + } = prevConfigs; + if (prevContainsCreateStrategy) { + strategies.push(TaskPartitionStrategy.CREATE); + } + if (prevContainsDropStrategy) { + strategies.push(TaskPartitionStrategy.DROP); + } + } + return { + __id: index, + containsCreateStrategy, + containsDropStrategy, + strategies, + tableName, + definitionCount: partition?.partitionDefinitions?.length, + partitionMode, + option: { + partitionKeyConfigs: partition.partitionOption?.columnNames?.map((name) => ({ + name, + })) ?? [ + { + name: partition.partitionOption?.expression, + }, + ], + }, + }; + }) + ?.filter(Boolean); + setTableConfigs(tableConfigs as ITableConfig[]); + } + }; + + const onClose = useCallback(async () => { + form?.resetFields(); + setCrontab(null); + setDropCrontab(null); + setDisabledSubmit(true); + setHasPartitionPlan(false); + setTableConfigs([]); + setCreatedOriginTableConfigs([]); + setCreatedTableConfigs([]); + scheduleStore.setPartitionPlanData(false); + if (mode === SchedulePageMode.MULTI_PAGE) { + await pageStore?.close?.(pageStore.activePageKey); + openSchedulesPage(SchedulePageType.PARTITION_PLAN); + } else { + history.back(); + } + }, [scheduleStore]); + + const closeWithConfirm = useCallback(() => { + Modal.confirm({ + title: formatMessage({ + id: 'odc.components.PartitionDrawer.AreYouSureYouWant', + defaultMessage: '确认取消新建分区计划吗?', + }), + //确认取消新建分区计划吗? + centered: true, + onOk() { + onClose(); + }, + }); + }, [onClose]); + + const handleCrontabChange = (crontab, isCustomStrategy = false) => { + if (isCustomStrategy) { + setDropCrontab(crontab); + } else { + setCrontab(crontab); + } + }; + + const handleSubmit = async (scheduleName?: string) => { + try { + const values = await form.validateFields(); + if (!scheduleName) { + setOpen(true); + return; + } + const { databaseId, timeoutMillis, errorStrategy, triggerStrategy, startAt } = values; + const validTableConfigs = tableConfigs?.filter((config) => config?.strategies?.length); + const validHistoryOriginTableConfigs = historyOriginTableConfigs?.filter((item) => { + return !validTableConfigs?.some((config) => config?.tableName === item?.tableName); + }); + let partitionTableConfigs: IPartitionTableConfig[] = validTableConfigs + ?.map((config) => { + const createdOriginTableConfig = createdOriginTableConfigs?.find( + (item) => item.tableName === config.tableName, + ); + if (!config?.__isCreate) { + return createdOriginTableConfig ? createdOriginTableConfig : null; + } + const { + generateCount, + nameRuleType, + generateExpr, + keepLatestCount, + reloadIndexes, + namingPrefix, + namingSuffixExpression, + namingSuffixStrategy, + refPartitionKey, + intervalGenerateExpr, + tableName, + strategies, + option, + } = config; + const partitionKeyConfigs: { + partitionKeyInvoker: PARTITION_KEY_INVOKER; + strategy: TaskPartitionStrategy; + partitionKeyInvokerParameters: Record; + }[] = + option?.partitionKeyConfigs?.map((item) => { + const { + name: partitionKey, + partitionKeyInvoker, + fromCurrentTime, + baseTimestampMillis, + generateExpr, + interval, + intervalPrecision, + intervalGenerateExpr, + incrementFieldType, + incrementFieldTypeInDate, + } = item; + + if (partitionKeyInvoker === PARTITION_KEY_INVOKER.CUSTOM_GENERATOR) { + return { + partitionKey, + partitionKeyInvoker, + strategy: TaskPartitionStrategy.CREATE, + partitionKeyInvokerParameters: { + generateCount, + partitionKey, + generateParameter: { + generateExpr, + intervalGenerateExpr, + }, + }, + }; + } else { + const tempPartitionKeyInvoker = getPartitionKeyInvokerByIncrementFieldType( + partitionKeyInvoker, + incrementFieldType, + ); + const currentTimeParameter = { + fromCurrentTime: fromCurrentTime === START_DATE.CURRENT_DATE, + baseTimestampMillis: baseTimestampMillis?.valueOf(), + fieldType: incrementFieldType, + // 数值 + numberInterval: intervalGenerateExpr, + // 时间日期 + timeFormat: incrementFieldTypeInDate, + }; + if (fromCurrentTime !== START_DATE.CUSTOM_DATE) { + delete currentTimeParameter.baseTimestampMillis; + } + if ( + [INCREAMENT_FIELD_TYPE.NUMBER, INCREAMENT_FIELD_TYPE.TIMESTAMP]?.includes( + incrementFieldType, + ) + ) { + delete currentTimeParameter.timeFormat; + } + if ( + [INCREAMENT_FIELD_TYPE.TIME_STRING, INCREAMENT_FIELD_TYPE.TIMESTAMP]?.includes( + incrementFieldType, + ) + ) { + delete currentTimeParameter.numberInterval; + } + return { + partitionKey, + partitionKeyInvoker: tempPartitionKeyInvoker, + strategy: TaskPartitionStrategy.CREATE, + partitionKeyInvokerParameters: { + generateCount, + partitionKey, + generateParameter: { + ...currentTimeParameter, + interval, + intervalPrecision, + }, + }, + }; + } + }) ?? []; + + if (strategies?.includes(TaskPartitionStrategy.DROP)) { + partitionKeyConfigs.push({ + partitionKeyInvoker: PARTITION_KEY_INVOKER.KEEP_MOST_LATEST_GENERATOR, + strategy: TaskPartitionStrategy.DROP, + partitionKeyInvokerParameters: { + keepLatestCount, + reloadIndexes, + }, + }); + } + const tableConfig = { + tableName, + partitionKeyConfigs: partitionKeyConfigs?.filter((item) => + strategies?.includes(item.strategy), + ), + partitionNameInvoker: null, + partitionNameInvokerParameters: {}, + }; + if (nameRuleType === 'PRE_SUFFIX') { + tableConfig.partitionNameInvoker = + PARTITION_NAME_INVOKER.DATE_BASED_PARTITION_NAME_GENERATOR; + tableConfig.partitionNameInvokerParameters = { + partitionNameGeneratorConfig: { + namingPrefix, + namingSuffixExpression, + namingSuffixStrategy, + refPartitionKey, + }, + }; + } else { + tableConfig.partitionNameInvoker = + PARTITION_NAME_INVOKER.CUSTOM_PARTITION_NAME_GENERATOR; + tableConfig.partitionNameInvokerParameters = { + partitionNameGeneratorConfig: { + generateExpr, + intervalGenerateExpr, + }, + }; + } + if (strategies?.length === 1 && strategies?.includes(TaskPartitionStrategy.DROP)) { + delete tableConfig.partitionNameInvokerParameters; + } + return tableConfig; + }) + ?.filter(Boolean); + if (validHistoryOriginTableConfigs?.length) { + partitionTableConfigs = partitionTableConfigs.concat(validHistoryOriginTableConfigs); + } + + const params: createScheduleRecord = { + type: ScheduleType.PARTITION_PLAN, + name: scheduleName, + triggerConfig: undefined, + id: isEdit ? partitionPlanData?.id : undefined, + parameters: { + databaseId, + enabled: true, + id: isEdit ? form.getFieldValue('id') : undefined, + partitionTableConfigs, + creationTrigger: null, + droppingTrigger: null, + timeoutMillis: hourToMilliSeconds(timeoutMillis), + errorStrategy, + }, + }; + switch (triggerStrategy) { + case TaskExecStrategy.TIMER: { + const { mode, dateType, cronString, hour, dayOfMonth, dayOfWeek } = crontab || {}; + params.parameters.creationTrigger = { + triggerStrategy: (mode === 'custom' ? 'CRON' : dateType) as TaskExecStrategy, + days: dateType === CrontabDateType.weekly ? dayOfWeek : dayOfMonth, + hours: hour, + cronExpression: cronString, + }; + break; + } + case TaskExecStrategy.START_AT: { + params.parameters.creationTrigger = { + triggerStrategy: TaskExecStrategy.START_AT, + startAt: startAt?.valueOf(), + }; + break; + } + default: { + params.parameters.creationTrigger = { + triggerStrategy: TaskExecStrategy.START_NOW, + }; + break; + } + } + params.triggerConfig = params.parameters.creationTrigger; + if (isCustomStrategy && triggerStrategy === TaskExecStrategy.TIMER) { + const { mode, dateType, cronString, hour, dayOfMonth, dayOfWeek } = dropCrontab || {}; + params.parameters.droppingTrigger = { + triggerStrategy: (mode === 'custom' ? 'CRON' : dateType) as TaskExecStrategy, + days: dateType === CrontabDateType.weekly ? dayOfWeek : dayOfMonth, + hours: hour, + cronExpression: cronString, + }; + } + setConfirmLoading(true); + let resCount; + if (isEdit) { + resCount = await updateSchedule(params); + } else { + resCount = await createSchedule(params); + } + setConfirmLoading(false); + if (resCount?.data) { + onClose(); + setCreateScheduleDatabase(undefined); + !isEdit && message.success('新建成功'); + isEdit && message.success('修改成功'); + } + } catch (e) { + console.log(e); + } + }; + + const handlePlansConfigChange = (configs: any[]) => { + const newConfigs = tableConfigs?.map((item) => { + const planValue = configs.find((value) => value.__id === item.__id); + return planValue ? planValue : item; + }); + setTableConfigs(newConfigs); + }; + useEffect(() => { + if (tableConfigs?.length) { + const disabledSubmit = tableConfigs?.some((item) => !item.strategies); + setDisabledSubmit(disabledSubmit); + } + }, [tableConfigs]); + + useEffect(() => { + loadData(); + }, [databaseId, sessionId]); + + useEffect(() => { + const databaseId = partitionPlanData?.databaseId; + const id = partitionPlanData?.id; + if (databaseId) { + form.setFieldsValue({ + databaseId, + }); + } + if (id) { + loadEditData(id); + } + return () => { + setCreateScheduleDatabase(undefined); + }; + }, []); + + useEffect(() => { + if (triggerStrategy !== TaskExecStrategy.TIMER) { + form.setFieldsValue({ + isCustomStrategy: false, + }); + } + }, [triggerStrategy]); + + const { run: fetchScheduleDetail, loading } = useRequest(getScheduleDetail, { + manual: true, + }); + + const loadEditData = async (editId: number) => { + const detailRes = (await fetchScheduleDetail(editId)) as IScheduleRecord; + setCreateScheduleDatabase(detailRes?.parameters?.databaseInfo); + const { parameters } = detailRes ?? {}; + const { creationTrigger, droppingTrigger } = parameters ?? {}; + const formData = { + ...parameters, + isCustomStrategy: !!detailRes?.parameters?.droppingTrigger, + timeoutMillis: milliSecondsToHour(detailRes?.parameters.timeoutMillis), + scheduleName: detailRes?.scheduleName, + triggerStrategy: detailRes?.triggerConfig?.triggerStrategy, + startAt: undefined, + }; + if (creationTrigger) { + const { triggerStrategy, cronExpression, hours, days, startAt } = creationTrigger ?? {}; + if (![TaskExecStrategy.START_NOW, TaskExecStrategy.START_AT].includes(triggerStrategy)) { + formData.triggerStrategy = TaskExecStrategy.TIMER; + const crontab = { + mode: + triggerStrategy === TaskExecStrategy.CRON ? CrontabMode.custom : CrontabMode.default, + dateType: triggerStrategy as any, + cronString: cronExpression, + hour: hours, + dayOfMonth: days, + dayOfWeek: days, + }; + setCrontab(crontab); + } + if (triggerStrategy === TaskExecStrategy.START_AT) { + formData.startAt = dayjs(startAt) as any; + } + } + if (droppingTrigger) { + const { triggerStrategy, cronExpression, hours, days } = droppingTrigger ?? {}; + crontabDropRef.current?.setValue({ + mode: triggerStrategy === TaskExecStrategy.CRON ? CrontabMode.custom : CrontabMode.default, + dateType: triggerStrategy as any, + cronString: cronExpression, + hour: hours, + dayOfMonth: days, + dayOfWeek: days, + }); + } + await form.setFieldsValue(formData); + setPreTableConfigs(parameters?.partitionTableConfigs); + const configs = parameters?.partitionTableConfigs?.map((config, index) => ({ + ...config, + __id: index, + containsCreateStrategy: config.partitionKeyConfigs?.some( + (c) => c.strategy === TaskPartitionStrategy.CREATE, + ), + containsDropStrategy: config.partitionKeyConfigs?.some( + (c) => c.strategy === TaskPartitionStrategy.DROP, + ), + option: { + partitionKeyConfigs: config.partitionKeyConfigs?.map((item) => ({ + ...item, + })), + }, + strategies: [ + ...(config.partitionKeyConfigs?.some((c) => c.strategy === TaskPartitionStrategy.CREATE) + ? [TaskPartitionStrategy.CREATE] + : []), + ...(config.partitionKeyConfigs?.some((c) => c.strategy === TaskPartitionStrategy.DROP) + ? [TaskPartitionStrategy.DROP] + : []), + ], + })); + setCreatedTableConfigs(getCreatedTableConfigs(configs)); + setCreatedOriginTableConfigs(configs); + setTableConfigs(getCreatedTableConfigs(configs)); + }; + + return ( +
+ + +
+

+ 基本信息 +

+ { + setCreateScheduleDatabase(db); + }} + onInit={(db) => setCreateScheduleDatabase(db)} + /> + {hasPartitionPlan && ( + + )} + + + + + + + +

+ 执行方式 +

+ + + + { + formatMessage({ + id: 'odc.DataArchiveTask.CreateModal.ExecuteNow', + defaultMessage: '立即执行', + }) /*立即执行*/ + } + + + { + formatMessage({ + id: 'odc.DataArchiveTask.CreateModal.ScheduledExecution', + defaultMessage: '定时执行', + }) /*定时执行*/ + } + + + { + formatMessage({ + id: 'odc.DataArchiveTask.CreateModal.PeriodicExecution', + defaultMessage: '周期执行', + }) /*周期执行*/ + } + + + + + {({ getFieldValue }) => { + const triggerStrategy = getFieldValue('triggerStrategy') || []; + + if (triggerStrategy === TaskExecStrategy.START_AT) { + return ( + + } + disabledDate={disabledDate} + disabledTime={disabledTime} + /> + + ); + } + if (triggerStrategy === TaskExecStrategy.TIMER) { + return ( + + { + handleCrontabChange(value); + }} + /> + + ); + } + }} + + + + + { + formatMessage({ + id: 'src.component.Task.PartitionTask.CreateModal.BE341FCE' /*自定义删除策略执行周期*/, + defaultMessage: '自定义删除策略执行周期', + }) /* 自定义删除策略执行周期 */ + } + + {!isCustomStrategy && ( + + + { + formatMessage({ + id: 'src.component.Task.PartitionTask.CreateModal.5DEF5FCE' /*未勾选时,删除策略执行周期将与创建一致*/, + defaultMessage: '未勾选时,删除策略执行周期将与创建一致', + }) /* 未勾选时,删除策略执行周期将与创建一致 */ + } + + + )} + + + {isCustomStrategy && ( + + { + handleCrontabChange(value, true); + }} + /> + + )} +

+ 作业设置 +

+ + + + + + + + + + + { + formatMessage({ + id: 'src.component.Task.PartitionTask.CreateModal.53678847' /*小时*/, + defaultMessage: '小时', + }) /* 小时 */ + } + + + +
+
+
+ + + + + + +
+ { + handleSubmit(scheduleName); + }} + /> +
+ ); +}; +export default inject('scheduleStore', 'pageStore')(observer(Create)); diff --git a/src/component/Schedule/modals/SQLPlan/Content/index.tsx b/src/component/Schedule/modals/SQLPlan/Content/index.tsx new file mode 100644 index 000000000..618f6b7ca --- /dev/null +++ b/src/component/Schedule/modals/SQLPlan/Content/index.tsx @@ -0,0 +1,191 @@ +import { IScheduleRecord, ISqlPlanParameters, ScheduleType } from '@/d.ts/schedule'; +import { Descriptions, Divider, Typography } from 'antd'; +import { getFormatDateTime, milliSecondsToHour } from '@/util/utils'; +import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; +import RiskLevelLabel from '@/component/RiskLevelLabel'; +import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; +import { formatMessage } from '@/util/intl'; +import { SQLContent } from '@/component/SQLContent'; +import { getDataSourceModeConfigByConnectionMode } from '@/common/datasource'; +import { TaskType } from '@/d.ts'; +import { IScheduleTaskRecord, scheduleTask } from '@/d.ts/scheduleTask'; +import { SubTypeTextMap } from '@/constant/scheduleTask'; +import EllipsisText from '@/component/EllipsisText'; + +const ErrorStrategy = { + ABORT: formatMessage({ + id: 'odc.component.DetailModal.sqlPlan.StopATask', + defaultMessage: '停止任务', + }), //停止任务 + CONTINUE: formatMessage({ + id: 'odc.component.DetailModal.sqlPlan.IgnoreErrorsToContinueThe', + defaultMessage: '忽略错误继续任务', + }), + //忽略错误继续任务 +}; + +interface IProps { + schedule: IScheduleRecord; + subTask?: scheduleTask; + theme?: string; +} +const SQLPlanScheduleContent: React.FC = (props) => { + const { schedule, theme, subTask } = props; + const { parameters } = schedule || {}; + const executionTimeout = milliSecondsToHour(parameters?.timeoutMillis); + + return ( + <> + + {subTask && ( + <> + {subTask?.id} + {SubTypeTextMap[subTask?.type]} + + )} + {!subTask && ( + <> + {schedule?.scheduleId} + SQL 计划 + + )} + +
+ +
+ } + /> +
+
+
+ + + + + + +
+ + {parameters && ( + + + + } + direction="column" + /> + )} + + + + {parameters?.delimiter} + + + {parameters?.queryLimit} + + + + {formatMessage( + { + id: 'odc.component.DetailModal.sqlPlan.ExecutiontimeoutHours', + defaultMessage: '{executionTimeout}小时', + }, + + { executionTimeout }, + )} + + + { + schedule?.allowConcurrent + ? formatMessage({ + id: 'odc.component.DetailModal.sqlPlan.IgnoreTheCurrentTaskStatus', + defaultMessage: '忽略当前任务状态,定期发起新任务', + }) //忽略当前任务状态,定期发起新任务 + : formatMessage({ + id: 'odc.component.DetailModal.sqlPlan.AfterTheCurrentTaskIs', + defaultMessage: '待当前任务执行完毕在新周期发起任务', + }) //待当前任务执行完毕在新周期发起任务 + } + + + {ErrorStrategy[parameters?.errorStrategy]} + + + + + + {schedule?.creator?.name || '-'} + + + {getFormatDateTime(schedule?.createTime)} + + + + ); +}; + +export default SQLPlanScheduleContent; diff --git a/src/component/Task/modals/SQLPlanTask/CreateModal/const.ts b/src/component/Schedule/modals/SQLPlan/Create/const.ts similarity index 100% rename from src/component/Task/modals/SQLPlanTask/CreateModal/const.ts rename to src/component/Schedule/modals/SQLPlan/Create/const.ts diff --git a/src/component/Task/modals/SQLPlanTask/CreateModal/index.less b/src/component/Schedule/modals/SQLPlan/Create/index.less similarity index 98% rename from src/component/Task/modals/SQLPlanTask/CreateModal/index.less rename to src/component/Schedule/modals/SQLPlan/Create/index.less index 14453dd57..d3e2b6236 100644 --- a/src/component/Task/modals/SQLPlanTask/CreateModal/index.less +++ b/src/component/Schedule/modals/SQLPlan/Create/index.less @@ -1,4 +1,4 @@ -.sql-plan { +.sqlPlan { .hide { display: none; } diff --git a/src/component/Schedule/modals/SQLPlan/Create/index.tsx b/src/component/Schedule/modals/SQLPlan/Create/index.tsx new file mode 100644 index 000000000..4106479f2 --- /dev/null +++ b/src/component/Schedule/modals/SQLPlan/Create/index.tsx @@ -0,0 +1,901 @@ +import { getDataSourceModeConfig } from '@/common/datasource'; +import { createTask, getAsyncTaskUploadUrl } from '@/common/network/task'; +import CommonIDE from '@/component/CommonIDE'; +import Crontab from '@/component/Crontab'; +import { CrontabDateType, CrontabMode, ICrontab } from '@/component/Crontab/interface'; +import FormItemPanel from '@/component/FormItemPanel'; +import ODCDragger from '@/component/OSSDragger2'; +import DescriptionInput from '@/component/Task/component/DescriptionInput'; +import { SQLContentType, TaskExecStrategy } from '@/d.ts'; +import login from '@/store/login'; +import { CreateScheduleContext } from '@/component/Schedule/context/createScheduleContext'; +import { useDBSession } from '@/store/sessionManager/hooks'; +import { formatMessage } from '@/util/intl'; +import { getLocale } from '@umijs/max'; +import { openSchedulesPage } from '@/store/helper/page'; +import { + AutoComplete, + Button, + DatePicker, + Form, + InputNumber, + message, + Modal, + Radio, + Space, + Spin, +} from 'antd'; +import type { UploadFile } from 'antd/lib/upload/interface'; +import Cookies from 'js-cookie'; +import { inject, observer } from 'mobx-react'; +import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; +import DatabaseSelect from '@/component/Task/component/DatabaseSelect'; +import { useRequest } from 'ahooks'; +import styles from './index.less'; +import setting from '@/store/setting'; +import { rules } from './const'; +import { Rule } from '@@node_modules/antd/es/form'; +import AnchorContainer from '@/component/AnchorContainer'; +import { history } from '@umijs/max'; +import { ScheduleStore } from '@/store/schedule'; +import CreateTaskConfirmModal from '@/component/Task/component/CreateTaskConfirmModal'; +import { createSchedule, updateSchedule } from '@/common/network/schedule'; +import { FieldTimeOutlined } from '@ant-design/icons'; +import { disabledDate, disabledTime } from '@/util/utils'; +import { getScheduleDetail } from '@/common/network/schedule'; +const MAX_FILE_SIZE = 1024 * 1024 * 256; +import dayjs from 'dayjs'; +import { + IScheduleRecord, + ScheduleType, + createScheduleRecord, + createSqlPlanParameters, + ISqlPlanParameters, + SchedulePageType, +} from '@/d.ts/schedule'; +import { SchedulePageMode } from '@/component/Schedule/interface'; +import { PageStore } from '@/store/page'; + +enum ErrorStrategy { + CONTINUE = 'CONTINUE', + ABORT = 'ABORT', +} + +const defaultValue = { + sqlContentType: SQLContentType.TEXT, + delimiter: ';', + timeoutMillis: 48, + errorStrategy: ErrorStrategy.ABORT, + allowConcurrent: false, +}; +interface IProps { + scheduleStore?: ScheduleStore; + pageStore?: PageStore; + projectId?: number; + theme?: string; + mode?: SchedulePageMode; +} +const Create: React.FC = ({ scheduleStore, pageStore, projectId, theme, mode }) => { + const [sqlContentType, setSqlContentType] = useState(SQLContentType.TEXT); + const [formData, setFormData] = useState(null); + const [hasEdit, setHasEdit] = useState(false); + const [confirmLoading, setConfirmLoading] = useState(false); + const [crontab, setCrontab] = useState(null); + const [form] = Form.useForm(); + const databaseId = Form.useWatch('databaseId', form); + const { database } = useDBSession(databaseId); + const connection = database?.dataSource; + const crontabRef = useRef<{ + setValue: (value: ICrontab) => void; + resetFields: () => void; + }>(); + const { createScheduleDatabase, setCreateScheduleDatabase } = + useContext(CreateScheduleContext) || {}; + const [open, setOpen] = useState(false); + const { sqlPlanData } = scheduleStore; + const SQLPlanEditId = sqlPlanData?.id; + const isEdit = !!SQLPlanEditId && sqlPlanData?.type === 'EDIT'; + const { run: fetchScheduleDetail, loading } = useRequest(getScheduleDetail, { manual: true }); + + const isInitContent = useMemo(() => { + if (isEdit) { + return !!formData; + } else if (sqlPlanData?.type === 'RETRY') { + return !!formData; + } + return true; + }, [isEdit, sqlPlanData?.type, formData]); + + const loadEditData = async (editId: number) => { + const dataRes = (await fetchScheduleDetail(editId)) as IScheduleRecord; + setCreateScheduleDatabase(dataRes?.parameters?.databaseInfo); + const { parameters, triggerConfig, ...rest } = dataRes; + const { triggerStrategy, cronExpression, hours, days, startAt } = triggerConfig ?? {}; + const sqlContentType = parameters?.sqlObjectIds ? SQLContentType.FILE : SQLContentType.TEXT; + const formData = { + ...rest, + ...parameters, + triggerStrategy, + startAt: null, + sqlContentType, + sqlFiles: undefined, + timeoutMillis: parameters.timeoutMillis / 1000 / 60 / 60, + }; + + if (sqlContentType === SQLContentType.FILE) { + const sqlFiles = parameters?.sqlObjectIds?.map((id, i) => { + return { + uid: i, + name: parameters?.sqlObjectNames[i], + status: 'done', + response: { + data: { + contents: [{ objectId: id }], + }, + }, + }; + }); + formData.sqlFiles = sqlFiles; + } + if (![TaskExecStrategy.START_NOW, TaskExecStrategy.START_AT].includes(triggerStrategy)) { + formData.triggerStrategy = TaskExecStrategy.TIMER; + const crontab = { + mode: triggerStrategy === TaskExecStrategy.CRON ? CrontabMode.custom : CrontabMode.default, + dateType: triggerStrategy as any, + cronString: cronExpression, + hour: hours, + dayOfMonth: days, + dayOfWeek: days, + }; + setCrontab(crontab); + } + if (triggerStrategy === TaskExecStrategy.START_AT) { + formData.startAt = dayjs(startAt); + } + setSqlContentType(sqlContentType); + setFormData(formData); + form.setFieldsValue(formData); + }; + + const loadInitialDataFromSpaceConfig = () => { + form.setFieldValue( + 'queryLimit', + Number(setting.getSpaceConfigByKey('odc.sqlexecute.default.queryLimit')), + ); + }; + + useEffect(() => { + const databaseId = sqlPlanData?.databaseId; + if (databaseId) { + form.setFieldsValue({ + databaseId, + }); + } + + if (SQLPlanEditId) { + loadEditData(SQLPlanEditId); + } else { + loadInitialDataFromSpaceConfig(); + } + + return () => { + handleReset(); + }; + }, []); + + const setFormStatus = (fieldName: string, errorMessage: string) => { + form.setFields([ + { + name: [fieldName], + errors: errorMessage ? [errorMessage] : [], + }, + ]); + }; + + const handleCancel = async (hasEdit: boolean) => { + if (hasEdit) { + Modal.confirm({ + title: formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.AreYouSureYouWant', + defaultMessage: '是否确认取消此 SQL 计划?', + }), + //确认取消此 SQL 计划吗? + centered: true, + onOk: async () => { + scheduleStore.setSQLPlanData(false); + if (mode === SchedulePageMode.MULTI_PAGE) { + await pageStore?.close?.(pageStore.activePageKey); + await openSchedulesPage(SchedulePageType.SQL_PLAN); + } else { + history.back(); + } + }, + }); + } else { + scheduleStore.setSQLPlanData(false); + if (mode === SchedulePageMode.MULTI_PAGE) { + await pageStore?.close?.(pageStore.activePageKey); + openSchedulesPage(SchedulePageType.SQL_PLAN); + } else { + history.back(); + } + } + }; + + const handleCrontabChange = (crontab) => { + setCrontab(crontab); + }; + + const getFileIdAndNames = (files: UploadFile[]) => { + const ids = []; + const names = []; + files + ?.filter((file) => file?.status === 'done') + ?.forEach((file) => { + ids.push(file?.response?.data?.contents?.[0]?.objectId); + names.push(file?.name); + }); + return { + ids, + names, + size: ids.length, + }; + }; + + const checkFileSizeAmount = (files: UploadFile[]) => { + const fileSizeAmount = files?.reduce((prev, current) => { + return prev + current.size; + }, 0); + if (fileSizeAmount > MAX_FILE_SIZE) { + /** + * 校验文件总大小 + */ + message.warning( + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.UpToMbOfFiles', + defaultMessage: '文件最多不超过 256 MB', + }), + //文件最多不超过 256MB + ); + return false; + } + return true; + }; + + const handleCreate = async (data: Partial>) => { + const res = await createSchedule(data); + setCreateScheduleDatabase(undefined); + handleCancel(false); + setConfirmLoading(false); + if (res.data) { + message.success('新建成功'); + } + }; + + const handleEdit = async (data: Partial>) => { + data.id = SQLPlanEditId; + const res = await updateSchedule(data); + setConfirmLoading(false); + if (res?.data) { + handleCancel(false); + message.success('修改成功'); + } + }; + + const handleEditAndConfirm = async ( + data: Partial>, + ) => { + Modal.confirm({ + title: formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.AreYouSureYouWant.1', + defaultMessage: '是否确认修改此 SQL 计划?', + }), + //确认要修改此 SQL 计划吗? + content: ( + <> +
+ { + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.EditSqlPlan', + defaultMessage: '编辑 SQL 计划', + }) + /*编辑 SQL 计划*/ + } +
+
+ { + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.TheTaskNeedsToBe', + defaultMessage: '任务需要重新审批,审批通过后此任务将重新执行', + }) + /*任务需要重新审批,审批通过后此任务将重新执行*/ + } +
+ + ), + + cancelText: formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.Cancel', + defaultMessage: '取消', + }), + //取消 + okText: formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.Ok', + defaultMessage: '确定', + }), //确定 + centered: true, + onOk: () => { + handleEdit(data); + }, + onCancel: () => { + setConfirmLoading(false); + }, + }); + }; + + const handleSubmit = async (scheduleName?: string) => { + form + .validateFields() + .then(async (values) => { + const { + databaseId, + sqlContentType, + sqlContent, + sqlFiles, + timeoutMillis, + queryLimit, + delimiter, + errorStrategy, + triggerStrategy, + startAt, + } = values; + const sqlFileIdAndNames = getFileIdAndNames(sqlFiles); + const { mode, dateType, cronString, hour, dayOfMonth, dayOfWeek } = crontab || {}; + const parameters: createSqlPlanParameters = { + databaseId, + sqlContent, + sqlObjectNames: sqlFileIdAndNames?.names, + sqlObjectIds: sqlFileIdAndNames?.ids, + timeoutMillis: timeoutMillis ? timeoutMillis * 60 * 60 * 1000 : undefined, + errorStrategy, + delimiter, + queryLimit, + modifyTimeoutIfTimeConsumingSqlExists: true, + }; + if (!checkFileSizeAmount(sqlFiles)) { + return; + } + if (sqlContentType === SQLContentType.FILE) { + delete parameters.sqlContent; + if (sqlFiles?.some((item) => item?.error?.isLimit)) { + setFormStatus( + 'sqlFiles', + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.UpToMbOfFiles', + defaultMessage: '文件最多不超过 256 MB', + }), + //文件最多不超过 256MB + ); + return; + } + + if (!sqlFileIdAndNames?.size || sqlFileIdAndNames?.size !== sqlFiles?.length) { + setFormStatus( + 'sqlFiles', + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.PleaseUploadTheSqlFile', + defaultMessage: '请上传 SQL 文件', + }), + //请上传 SQL 文件 + ); + return; + } + } else { + delete parameters.sqlObjectIds; + delete parameters.sqlObjectNames; + } + if (!scheduleName) { + setOpen(true); + return; + } + const data: createScheduleRecord = { + name: scheduleName, + type: ScheduleType.SQL_PLAN, + parameters, + triggerConfig: null, + }; + switch (triggerStrategy) { + case TaskExecStrategy.TIMER: { + data.triggerConfig = { + triggerStrategy: (mode === 'custom' ? 'CRON' : dateType) as TaskExecStrategy, + days: dateType === CrontabDateType.weekly ? dayOfWeek : dayOfMonth, + hours: hour, + cronExpression: cronString, + }; + break; + } + case TaskExecStrategy.START_AT: { + data.triggerConfig = { + triggerStrategy: TaskExecStrategy.START_AT, + startAt: startAt?.valueOf(), + }; + break; + } + default: { + data.triggerConfig = { + triggerStrategy: TaskExecStrategy.START_NOW, + }; + } + } + setConfirmLoading(true); + if (isEdit) { + handleEditAndConfirm(data); + } else { + handleCreate(data); + } + }) + .catch((errorInfo) => { + form.scrollToField(errorInfo?.errorFields?.[0]?.name); + console.error(JSON.stringify(errorInfo)); + }); + }; + + const handleContentTypeChange = (e) => { + setSqlContentType(e.target.value); + }; + + const handleSqlChange = (sql: string) => { + form?.setFieldsValue({ + sqlContent: sql, + }); + + setHasEdit(true); + }; + + const handleFieldsChange = () => { + setHasEdit(true); + }; + + const handleBeforeUpload = (file) => { + const isLt20M = MAX_FILE_SIZE > file.size; + if (!isLt20M) { + setTimeout(() => { + setFormStatus( + 'sqlFiles', + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.UpToMbOfFiles', + defaultMessage: '文件最多不超过 256 MB', + }), + //文件最多不超过 256MB + ); + }, 0); + } + return isLt20M; + }; + + const handleFileChange = (files: UploadFile[]) => { + form?.setFieldsValue({ + sqlFiles: files, + }); + + if (files.some((item) => item?.error?.isLimit)) { + setFormStatus( + 'sqlFiles', + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.UpToMbOfFiles', + defaultMessage: '文件最多不超过 256 MB', + }), + //文件最多不超过 256MB + ); + } else { + setFormStatus('sqlFiles', ''); + } + }; + + const draggerProps = { + accept: '.sql', + uploadFileOpenAPIName: 'UploadFile', + onBeforeUpload: handleBeforeUpload, + multiple: true, + tip: formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.YouCanDragAndDrop', + defaultMessage: '支持拖拽文件上传,任务将按文件排列的先后顺序执行', + }), + //支持拖拽文件上传,任务将按文件排列的先后顺序执行 + maxCount: 500, + action: getAsyncTaskUploadUrl(), + headers: { + 'X-XSRF-TOKEN': Cookies.get('XSRF-TOKEN') || '', + 'Accept-Language': getLocale(), + currentOrganizationId: login.organizationId?.toString(), + }, + + defaultFileList: formData?.sqlFiles, + onFileChange: handleFileChange, + }; + + const handleReset = () => { + setFormData(null); + setSqlContentType(SQLContentType.TEXT); + form?.resetFields(); + setCrontab(null); + setCreateScheduleDatabase(undefined); + }; + + return ( +
+ + +
+

+ 基本信息 +

+ { + setCreateScheduleDatabase(db); + }} + onInit={(db) => setCreateScheduleDatabase(db)} + /> + + + + + + {isInitContent && ( + + )} + + + {isInitContent && ( + +

+ { + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.ClickOrDragMultipleFiles', + defaultMessage: '点击或将多个文件拖拽到这里上传', + }) + /*点击或将多个文件拖拽到这里上传*/ + } +

+

+ { + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.TheFileCanBeUp', + defaultMessage: '文件最多不超过 256 MB ,支持扩展名 .sql', + }) + /*文件最多不超过 256MB ,支持扩展名 .sql*/ + } +

+
+ )} +
+ + + { + return { + value, + }; + })} + /> + + + + + + + + + + { + formatMessage({ + id: 'odc.components.CreateSQLPlanTaskModal.Hours', + defaultMessage: '小时', + }) + /*小时*/ + } + + + + +

+ 执行方式 +

+ + + + { + formatMessage({ + id: 'odc.DataArchiveTask.CreateModal.ExecuteNow', + defaultMessage: '立即执行', + }) /*立即执行*/ + } + + + { + formatMessage({ + id: 'odc.DataArchiveTask.CreateModal.ScheduledExecution', + defaultMessage: '定时执行', + }) /*定时执行*/ + } + + + {' '} + { + formatMessage({ + id: 'odc.DataArchiveTask.CreateModal.PeriodicExecution', + defaultMessage: '周期执行', + }) /*周期执行*/ + } + + + + + {({ getFieldValue }) => { + const triggerStrategy = getFieldValue('triggerStrategy') || []; + if (triggerStrategy === TaskExecStrategy.START_AT) { + return ( + + } + disabledDate={disabledDate} + disabledTime={disabledTime} + /> + + ); + } + if (triggerStrategy === TaskExecStrategy.TIMER) { + return ( + + { + handleCrontabChange(value); + }} + /> + + ); + } + }} + +
+

+ 作业设置 +

+ + + + + + + + + +
+
+
+ + + + +
+ { + handleSubmit(scheduleName); + }} + /> +
+ ); +}; +export default inject('scheduleStore', 'pageStore')(observer(Create)); diff --git a/src/component/Table/MiniTable/index.tsx b/src/component/Table/MiniTable/index.tsx index d36950960..c7e624d96 100644 --- a/src/component/Table/MiniTable/index.tsx +++ b/src/component/Table/MiniTable/index.tsx @@ -39,6 +39,7 @@ interface IProps extends TableProps { enableEditTable?: boolean; columns: IColumnsType; isScroll?: boolean; + itemHigeht?: number; } export default function MiniTable({ @@ -48,6 +49,7 @@ export default function MiniTable({ enableEditTable = false, columns: PropColumns = [], isScroll = false, + itemHigeht = 40, ...restProps }: IProps) { const [pageSize, setPageSize] = useState(0); @@ -62,13 +64,13 @@ export default function MiniTable({ function resize() { const height = domRef.current.clientHeight - 24 - 60; console.log('resize', height); - setPageSize(Math.floor(height / 40)); + setPageSize(Math.floor(height / itemHigeht)); } const height = domRef.current.clientHeight - 24 - 60; if (isScroll) { setScrollHeight(domRef.current.clientHeight - 60); } - setPageSize(Math.floor(height / 40)); + setPageSize(Math.floor(height / itemHigeht)); const obsever = new ResizeObserver(() => { resize(); }); diff --git a/src/component/Task/component/ActionBar/helper.tsx b/src/component/Task/component/ActionBar/helper.tsx deleted file mode 100644 index 25d856acd..000000000 --- a/src/component/Task/component/ActionBar/helper.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { TaskStatus, TaskType } from '@/d.ts'; -import modalStore from '@/store/modal'; -import { formatMessage } from '@/util/intl'; - -export const actions = { - [TaskType.DATA_ARCHIVE]: (args) => modalStore.changeDataArchiveModal(true, args), - [TaskType.DATA_DELETE]: (args) => modalStore.changeDataClearModal(true, args), - [TaskType.SQL_PLAN]: (args) => modalStore.changeCreateSQLPlanTaskModal(true, args), - [TaskType.LOGICAL_DATABASE_CHANGE]: (args) => modalStore.changeLogicialDatabaseModal(true, args), - [TaskType.ASYNC]: (args) => modalStore.changeCreateAsyncTaskModal(true, args), - [TaskType.DATAMOCK]: (args) => modalStore.changeDataMockerModal(true, args), - [TaskType.APPLY_DATABASE_PERMISSION]: (args) => - modalStore.changeApplyDatabasePermissionModal(true, args), - [TaskType.APPLY_TABLE_PERMISSION]: (args) => - modalStore.changeApplyTablePermissionModal(true, args), - [TaskType.MULTIPLE_ASYNC]: (args) => modalStore.changeMultiDatabaseChangeModal(true, args), - [TaskType.SHADOW]: (args) => modalStore.changeShadowSyncVisible(true, args), - [TaskType.STRUCTURE_COMPARISON]: (args) => modalStore.changeStructureComparisonModal(true, args), - [TaskType.EXPORT]: (args) => modalStore.changeExportModal(true, args), - [TaskType.IMPORT]: (args) => modalStore.changeImportModal(true, args), - [TaskType.EXPORT_RESULT_SET]: (args) => - modalStore.changeCreateResultSetExportTaskModal(true, args), - [TaskType.ONLINE_SCHEMA_CHANGE]: (args) => modalStore.changeCreateDDLAlterTaskModal(true, args), - [TaskType.PARTITION_PLAN]: (args) => modalStore.changePartitionModal(true, args), -}; -/** 周期任务 */ - -export const SCHEDULE_TASKS = [TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE, TaskType.SQL_PLAN]; - -/** 作业调度任务 */ -export const JOB_SCHEDULE_TASKS = [TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE]; - -export const actionInfo = { - reTryBtn: { - key: 'reTry', - text: formatMessage({ - id: 'src.component.Task.component.ActionBar.C324AD20', - defaultMessage: '再次发起', - }), //'再次发起' - type: 'button', - }, - editBtn: { - key: 'edit', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Edit', - defaultMessage: '编辑', - }), //编辑 - type: 'button', - }, - stopBtn: { - key: 'stop', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Termination', - defaultMessage: '终止', - }), //终止 - type: 'button', - }, - disableBtn: { - key: 'disable', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Disable', - defaultMessage: '禁用', - }), //禁用 - type: 'button', - }, - stopScheduleTaskBtn: { - key: 'stopLogicalChangeTask', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Termination', - defaultMessage: '终止', - }), //终止 - type: 'button', - }, - enableBtn: { - key: 'enable', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Enable', - defaultMessage: '启用', - }), //启用 - type: 'button', - }, - deleteBtn: { - key: 'delete', - text: formatMessage({ - id: 'src.component.Task.component.ActionBar.E16B982C', - defaultMessage: '删除', - }), - type: 'button', - }, - closeBtn: { - key: 'close', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Close', - defaultMessage: '关闭', - }), - type: 'button', - }, - copyBtn: { - key: 'copy', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Copy', - defaultMessage: '复制', - }), - type: 'button', - }, - rollbackBtn: { - key: 'rollback', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.RollBack', - defaultMessage: '回滚', - }), - type: 'button', - }, - executeBtn: { - key: 'execute', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Run', - defaultMessage: '执行', - }), - type: 'button', - }, - approvalBtn: { - key: 'approval', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Pass', - defaultMessage: '通过', - }), - type: 'button', - }, - againBtn: { - key: 'again', - text: formatMessage({ - id: 'src.component.Task.component.ActionBar.57DBF8A7', - defaultMessage: '重试', - }), - type: 'button', - }, - downloadBtn: { - key: 'download', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Download', - defaultMessage: '下载', - }), - type: 'button', - }, - downloadSQLBtn: { - key: 'downloadSQL', - text: formatMessage({ - id: 'src.component.Task.component.ActionBar.DBA6CB6E', - defaultMessage: '下载 SQL', - }), //'下载 SQL' - type: 'button', - }, - structrueComparisonBySQL: { - key: 'structrueComparisonBySQL', - text: formatMessage({ - id: 'src.component.Task.component.ActionBar.46F2F0ED', - defaultMessage: '发起结构同步', - }), //'发起结构同步' - type: 'button', - }, - openLocalFolder: { - key: 'openLocalFolder', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.OpenFolder', - defaultMessage: '打开文件夹', - }), - type: 'button', - }, - downloadViewResultBtn: { - key: 'downloadViewResult', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.DownloadQueryResults', - defaultMessage: '下载查询结果', - }), - type: 'button', - }, -}; diff --git a/src/component/Task/component/ActionBar/index.tsx b/src/component/Task/component/ActionBar/index.tsx deleted file mode 100644 index 56a6a816d..000000000 --- a/src/component/Task/component/ActionBar/index.tsx +++ /dev/null @@ -1,1170 +0,0 @@ -/* - * 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 { - againTask, - createTask, - downloadTaskFlow, - executeTask, - getStructureComparisonTaskFile, - getTaskResult, - stopTask, - stopDataArchiveSubTask, - getDataArchiveSubTask, - getTaskDetail, - getAsyncResultSet, -} from '@/common/network/task'; -import Action from '@/component/Action'; -import { TaskTypeMap } from '@/component/Task/component/TaskTable/const'; -import type { - IApplyPermissionTaskParams, - ICycleSubTaskRecord, - ICycleTaskRecord, - ILogicalDatabaseAsyncTaskParams, -} from '@/d.ts'; -import { - IApplyDatabasePermissionTaskParams, - IApplyTablePermissionTaskParams, - IAsyncTaskParams, - IMockDataParams, - IMultipleAsyncTaskParams, - ITaskResult, - RollbackType, - SubTaskStatus, - TaskDetail, - TaskExecStrategy, - TaskOperationType, - TaskRecord, - TaskRecordParameters, - TaskStatus, - TaskType, - IResultSetExportTaskParams, - ISqlExecuteResultStatus, -} from '@/d.ts'; -import type { UserStore } from '@/store/login'; -import type { ModalStore } from '@/store/modal'; -import type { SettingStore } from '@/store/setting'; -import type { TaskStore } from '@/store/task'; -import ipcInvoke from '@/util/client/service'; -import { isClient } from '@/util/env'; -import { formatMessage } from '@/util/intl'; -import { downloadFile, getLocalFormatDateTime, uniqueTools } from '@/util/utils'; -import { message, Modal, Popconfirm, Tooltip } from 'antd'; -import { inject, observer } from 'mobx-react'; -import React, { useEffect, useMemo, useState } from 'react'; -import { isCycleTask, isLogicalDbChangeTask } from '@/component/Task/helper'; -import RollBackModal from '../RollbackModal'; -import { ProjectRole } from '@/d.ts/project'; -import { useRequest } from 'ahooks'; -import { taskSuccessHintInfo } from '@/constant'; -import { actionInfo, actions, JOB_SCHEDULE_TASKS, SCHEDULE_TASKS } from './helper'; -import { IAddOperationsParams } from './type'; -import { openSQLResultSetViewPage } from '@/store/helper/page'; -import setting from '@/store/setting'; -interface IProps { - userStore?: UserStore; - taskStore?: TaskStore; - settingStore?: SettingStore; - modalStore?: ModalStore; - isDetailModal?: boolean; - task: Partial | TaskDetail>; - disabledSubmit?: boolean; - result?: ITaskResult; - onReloadList?: () => void; - onReload?: () => void; - onApprovalVisible?: (status: boolean, visible: boolean) => void; - onDetailVisible: (task: TaskRecord, visible: boolean) => void; - onClose?: () => void; - delTaskList?: number[]; - setDelTaskList?: React.Dispatch>; -} - -const ActionBar: React.FC = inject( - 'taskStore', - 'userStore', - 'settingStore', - 'modalStore', -)( - observer((props) => { - const { - modalStore, - userStore: { user }, - settingStore, - isDetailModal, - task, - disabledSubmit = false, - result, - delTaskList = [], - taskStore, - setDelTaskList, - } = props; - /** 是否创建者 */ - const isOwner = user?.id === task?.creator?.id; - /** 可审批者 */ - const isApprover = task?.approvable; - /** 当前用户在当前项目中的角色 */ - const { currentUserResourceRoles = [] } = task?.project || {}; - const [activeBtnKey, setActiveBtnKey] = useState(null); - const [openRollback, setOpenRollback] = useState(false); - const [taskList, setTaskList] = useState([]); - const [viewLoading, setViewLoading] = useState(false); - const isSqlworkspace = location?.hash?.includes('sqlworkspace'); - const disabledApproval = - task?.status === TaskStatus.WAIT_FOR_CONFIRM && !isDetailModal ? true : disabledSubmit; - - useEffect(() => { - if (task?.id && isLogicalDbChangeTask(task?.type) && isDetailModal) { - loadtaskList(); - } - return cancel(); - }, [task?.id]); - - const getScheduleTask = async () => { - const taskList = await getDataArchiveSubTask(task?.id); - setTaskList(taskList?.contents); - return taskList?.contents; - }; - - const { run: loadtaskList, cancel } = useRequest(getScheduleTask, { - pollingInterval: 3000, - manual: true, - onSuccess: (data) => { - if ( - [SubTaskStatus.CANCELED, SubTaskStatus.DONE, SubTaskStatus.FAILED]?.includes( - data?.[0]?.status as any, - ) - ) { - cancel(); - } - }, - }); - - const _openTaskDetail = async () => { - props.onDetailVisible(task as TaskRecord, true); - }; - - const closeTaskDetail = async () => { - props.onDetailVisible(null, false); - }; - - const resetActiveBtnKey = () => { - setActiveBtnKey(null); - }; - - const _stopTask = async () => { - setActiveBtnKey('stop'); - const res = await stopTask(task.id); - if (res) { - message.success(taskSuccessHintInfo.terminate); - - props?.onReloadList?.(); - props?.onReload?.(); - } - }; - - const _executeTask = async () => { - setActiveBtnKey('execute'); - const res = await executeTask(task.id); - if (res) { - message.success(taskSuccessHintInfo.start); - closeTaskDetail(); - props?.onReloadList?.(); - } - }; - - const _deleteTask = async () => { - const { id } = task; - const res = await createTask({ - taskType: TaskType.ALTER_SCHEDULE, - parameters: { - taskId: id, - operationType: 'DELETE', - }, - }); - if (res) { - setDelTaskList?.([...delTaskList, id]); - message.success(taskSuccessHintInfo.delete); - props?.onReloadList?.(); - } - }; - - const download = async () => { - downloadTaskFlow(task.id); - }; - - const downloadViewResult = async () => { - downloadFile(result?.zipFileDownloadUrl); - }; - - const handleCopy = () => {}; - - const confirmRollback = async (type: RollbackType) => { - closeTaskDetail(); - actions[TaskType.ASYNC]({ - type, - task: task as TaskDetail, - databaseId: task?.database?.id, - objectId: result?.rollbackPlanResult?.resultFileDownloadUrl, - parentFlowInstanceId: task?.id, - }); - }; - - useEffect(() => { - if (activeBtnKey) { - resetActiveBtnKey(); - } - }, [task?.status]); - - const _rollbackTask = async () => { - setOpenRollback(true); - }; - - const handleCloseRollback = async () => { - setOpenRollback(false); - }; - - const _approvalTask = async (status: boolean) => { - props.onApprovalVisible(status, true); - }; - - const _retryTask = async () => { - const { type } = task; - - switch (type) { - case TaskType.ASYNC: - case TaskType.DATAMOCK: { - const detailRes = (await getTaskDetail(task?.id)) as TaskDetail; - actions[type]({ task: detailRes }); - return; - } - case TaskType.APPLY_DATABASE_PERMISSION: { - actions[type]({ - task: task as TaskDetail, - }); - return; - } - case TaskType.APPLY_PROJECT_PERMISSION: { - modalStore.changeApplyPermissionModal(true, { - task: task as TaskDetail, - }); - return; - } - case TaskType.APPLY_TABLE_PERMISSION: { - actions[type]({ - task: task as TaskDetail, - }); - return; - } - case TaskType.MULTIPLE_ASYNC: { - actions[type]({ - projectId: (task as TaskDetail)?.parameters?.projectId, - task: task as TaskDetail, - }); - return; - } - case TaskType.SHADOW: - case TaskType.STRUCTURE_COMPARISON: - case TaskType.EXPORT: - case TaskType.IMPORT: - case TaskType.ONLINE_SCHEMA_CHANGE: - case TaskType.PARTITION_PLAN: { - actions[type]({ - databaseId: task.database?.id, - taskId: task?.id, - }); - return; - } - case TaskType.EXPORT_RESULT_SET: { - const detailRes = (await getTaskDetail( - task?.id, - )) as TaskDetail; - actions[type]({ - databaseId: task.database?.id, - taskId: task?.id, - sql: detailRes.parameters.sql, - task: detailRes, - }); - return; - } - default: { - const { database, executionStrategy, executionTime, parameters, description } = task; - - const data = { - taskType: type, - parameters, - databaseId: database?.id, - executionStrategy, - executionTime, - description, - }; - - const res = await createTask(data); - if (res) { - message.success(taskSuccessHintInfo.retry); //再次发起成功 - } - } - } - }; - - const _againTask = async () => { - const { id } = task; - - const res = await againTask({ id: id }); - if (res) { - message.success(taskSuccessHintInfo.again); - props?.onReloadList?.(); - props?.onReload?.(); - } - }; - - const viewResult = async () => { - setViewLoading(true); - try { - const resultSets = await getAsyncResultSet(task.id); - if (resultSets) { - /** - * 没有成功的请求的话,这里就不去展示结果了。 - */ - - const haveSuccessQuery = !!resultSets?.find( - (result) => result.status === ISqlExecuteResultStatus.SUCCESS && result.columns?.length, - ); - - if (!haveSuccessQuery) { - message.warning( - formatMessage({ - id: 'src.component.Task.component.ActionBar.797981FE', - defaultMessage: '无可查看的结果信息', - }), - ); - return; - } - if (isSqlworkspace) { - await openSQLResultSetViewPage( - task.id, - resultSets, - (task?.parameters as IAsyncTaskParams)?.sqlContent, - ); - taskStore.changeTaskManageVisible(false); - props.onDetailVisible(null, false); - } else { - window.open( - location.origin + - location.pathname + - `#/sqlworkspace?taskId=${task.id}&resultSets=${true}&sqlContent=${JSON.stringify( - (task?.parameters as IAsyncTaskParams)?.sqlContent, - )}`, - ); - } - } - } finally { - setViewLoading(false); - } - }; - - const _editCycleTask = async () => { - props?.onClose?.(); - const actionType = JOB_SCHEDULE_TASKS.includes(task?.type) ? task?.type : TaskType.SQL_PLAN; - actions?.[actionType]?.({ - id: task?.id, - type: JOB_SCHEDULE_TASKS.includes(task?.type) ? ('EDIT' as 'EDIT') : undefined, - }); - }; - - const _retryCycleTask = async () => { - props?.onClose?.(); - switch (task?.type) { - case TaskType.DATA_ARCHIVE: - case TaskType.DATA_DELETE: { - actions?.[task?.type]?.({ - id: task?.id, - type: 'RETRY', - }); - break; - } - case TaskType.LOGICAL_DATABASE_CHANGE: { - actions?.[task?.type]?.({ - task: task, - }); - break; - } - case TaskType.SQL_PLAN: { - actions?.[task?.type]?.({ - databaseId: task.database?.id, - taskId: task?.id, - }); - return; - } - } - }; - - const _stopScheduleTask = async () => { - await stopDataArchiveSubTask(task?.id, taskList?.[0]?.id); - await getScheduleTask(); - props?.onReload?.(); - }; - - const _alterScheduleTask = async ({ databaseId, operationType }) => { - await createTask({ - databaseId, - taskType: TaskType.ALTER_SCHEDULE, - parameters: { - taskId: task.id, - operationType, - }, - }); - props?.onReload?.(); - }; - - const handleTaskOperation = async ({ - operationType, - callback, - }: { - operationType: TaskOperationType; - callback?: () => void; - }) => { - let databaseId; - if (task?.database) { - databaseId = task?.database?.id; - } else { - databaseId = (task as ICycleTaskRecord)?.jobParameters - ?.databaseId; - } - const taskTypeName = TaskTypeMap[task?.type]; - const config = { - [TaskOperationType.RESUME]: { - title: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.AreYouSureYouWant.2', - defaultMessage: '是否确认启用此 SQL 计划?', - }), - content: ( - <> -
- { - formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.EnableSqlScheduling', - defaultMessage: '启用 SQL 计划', - }) /*启用 SQL 计划*/ - } -
-
- { - formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.TheTaskNeedsToBe.1', - defaultMessage: '任务需要重新审批,审批通过后此任务将启用', - }) /*任务需要重新审批,审批通过后此任务将启用*/ - } -
- - ), - }, - [TaskOperationType.PAUSE]: { - title: formatMessage( - { - id: 'src.component.Task.component.ActionBar.5495D4C7', - defaultMessage: '确认要禁用此{TaskTypeMapTaskType}?', - }, - { TaskTypeMapTaskType: taskTypeName }, - ), - content: ( - <> -
- {formatMessage( - { - id: 'src.component.Task.component.ActionBar.EC0C09D6', - defaultMessage: '禁用{TaskTypeMapTaskType}', - }, - { TaskTypeMapTaskType: taskTypeName }, - )} -
-
- { - formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.TheTaskNeedsToBe', - defaultMessage: '任务需要重新审批,审批通过后此任务将禁用', - }) /*任务需要重新审批,审批通过后此任务将禁用*/ - } -
- - ), - }, - [TaskOperationType.TERMINATE]: { - title: formatMessage( - { - id: 'src.component.Task.component.ActionBar.718054C5', - defaultMessage: '确认要终止此{taskTypeName}?', - }, - { taskTypeName }, - ), - content: ( - <> -
- {formatMessage({ - id: 'src.component.Task.component.ActionBar.5E24502A', - defaultMessage: '任务终止后将不可恢复', - })} -
- - ), - }, - }; - const { title, content } = config[operationType] || {}; - - Modal.confirm({ - title, - content, - cancelText: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Cancel', - defaultMessage: '取消', - }), //取消 - okText: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Ok.2', - defaultMessage: '确定', - }), //确定 - centered: true, - onOk: async () => { - callback?.(); - await _alterScheduleTask({ databaseId, operationType }); - }, - }); - }; - - /** - * 判断是否是创建人、项目DBA、项目owner,以操作工单 - */ - const haveOperationPermission = useMemo(() => { - return ( - currentUserResourceRoles?.some((item) => - [ProjectRole.DBA, ProjectRole.OWNER].includes(item), - ) || isOwner - ); - }, [currentUserResourceRoles, isOwner]); - - /** 添加再次发起,只有创建人才可以 */ - const setBtnByCreater = (tools, reTryBtn) => { - if (isOwner) { - tools.push(reTryBtn); - } - }; - const commonButtonConfig = { - viewBtn: { - key: 'view', - text: formatMessage({ id: 'odc.TaskManagePage.AsyncTask.See', defaultMessage: '查看' }), // 查看 - action: _openTaskDetail, - type: 'button', - isOpenBtn: true, - }, - rejectBtn: { - key: 'reject', - text: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.Reject', - defaultMessage: '拒绝', - }), - - //拒绝 - type: 'button', - action: async () => { - _approvalTask(false); - }, - }, - }; - - const getTaskTools = (_task) => { - let tools = []; - const addOperations = ({ auth, taskTypeLimit, operations }: IAddOperationsParams) => { - const hasOperaitons = Boolean(operations?.length); - const useableTaskType = !taskTypeLimit || taskTypeLimit?.includes(task?.type); - if (auth && useableTaskType && hasOperaitons) { - tools.push(...operations); - return true; - } - return false; - }; - - if (!_task) { - return []; - } - const { status, completeTime = 0 } = _task; - const structureComparisonData = - modalStore?.structureComparisonDataMap?.get(_task?.id) || null; - // 文件过期判断。 - const isExpired = Math.abs(Date.now() - completeTime) >= 14 * 24 * 60 * 60 * 1000 || false; - // 结构比对工单详情 任务未得到执行结果前禁用按钮。 - const disableBtn = - task?.type === TaskType.STRUCTURE_COMPARISON && - structureComparisonData && - ![SubTaskStatus.DONE, SubTaskStatus.FAILED].includes(structureComparisonData?.status); - // 结构比对结果均为一致时,无须发起数据库变更任务。 - const noAction = - SubTaskStatus.DONE === structureComparisonData?.status && - ((structureComparisonData?.overSizeLimit && structureComparisonData?.storageObjectId) || - (!structureComparisonData?.overSizeLimit && - !Boolean(structureComparisonData?.totalChangeScript?.length))); - const buttonConfig = { - ...commonButtonConfig, - closeBtn: { - ...actionInfo.closeBtn, - //关闭 - action: closeTaskDetail, - }, - copyBtn: { - ...actionInfo.copyBtn, - //复制 - action: handleCopy, - - isOpenBtn: true, - }, - rollbackBtn: { - ...actionInfo.rollbackBtn, - //回滚 - action: _rollbackTask, - }, - stopBtn: { - ...actionInfo.stopBtn, - //终止 - action: _stopTask, - }, - executeBtn: { - ...actionInfo.executeBtn, - //执行 - action: _executeTask, - isOpenBtn: true, - isPrimary: isDetailModal, - disabled: false, - tooltip: '', - }, - approvalBtn: { - ...actionInfo.approvalBtn, - //通过 - isPrimary: isDetailModal, - disabled: disabledApproval, - tooltip: disabledApproval - ? formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.SetPartitionPoliciesForAll', - defaultMessage: '请设置所有Range分区表的分区策略', - }) - : //请设置所有Range分区表的分区策略 - null, - action: async () => { - _approvalTask(true); - }, - }, - reTryBtn: { - ...actionInfo.reTryBtn, - //再次发起 - action: _retryTask, - }, - againBtn: { - ...actionInfo.againBtn, - // 重试 - action: _againTask, - }, - downloadBtn: { - ...actionInfo.downloadBtn, - disabled: isExpired, - isExpired, - tip: formatMessage({ - id: 'src.component.Task.component.ActionBar.F20AAC3F', - defaultMessage: '文件下载链接已超时,请重新发起工单。', - }), //'文件下载链接已超时,请重新发起工单。' - - action: download, - }, - downloadSQLBtn: { - ...actionInfo.downloadSQLBtn, - disabled: disableBtn || !structureComparisonData?.storageObjectId, - isExpired: disableBtn || !structureComparisonData?.storageObjectId, - tip: formatMessage({ - id: 'src.component.Task.component.ActionBar.A79907A3', - defaultMessage: '暂不可用', - }), //'暂不可用' - action: async () => { - if (structureComparisonData?.storageObjectId) { - const fileUrl = await getStructureComparisonTaskFile(_task?.id, [ - `${structureComparisonData?.storageObjectId}`, - ]); - fileUrl?.forEach((url) => { - url && downloadFile(url); - }); - } - }, - }, - structrueComparisonBySQL: { - ...actionInfo.structrueComparisonBySQL, - isExpired: disableBtn || noAction, - disabled: disableBtn || noAction, - tip: noAction - ? formatMessage({ - id: 'src.component.Task.component.ActionBar.D98B5B62', - defaultMessage: '结构一致,无需发起结构同步', - }) - : formatMessage({ - id: 'src.component.Task.component.ActionBar.4BF7D8BF', - defaultMessage: '暂不可用', - }), - isPrimary: true, - action: async () => { - structureComparisonData && - actions[TaskType.ASYNC]({ - sql: structureComparisonData?.totalChangeScript, - databaseId: structureComparisonData?.database?.id, - rules: null, - }); - }, - }, - openLocalFolder: { - ...actionInfo.openLocalFolder, - //打开文件夹 - action: async () => { - const info = await getTaskResult(task.id); - if (info?.exportZipFilePath) { - ipcInvoke('showItemInFolder', info?.exportZipFilePath); - } - }, - }, - downloadViewResultBtn: { - ...actionInfo.downloadViewResultBtn, - disabled: isExpired, - isExpired, - tip: formatMessage({ - id: 'src.component.Task.component.ActionBar.E9211B1A', - defaultMessage: '文件下载链接已超时,请重新发起工单。', - }), //'文件下载链接已超时,请重新发起工单。' - - action: downloadViewResult, - }, - }; - - const addRetryButton = () => { - setBtnByCreater(tools, buttonConfig.reTryBtn); - }; - - const resetToolsForApprover = ( - target?: Array<{ - key: string; - text: any; - type: string; - action: () => Promise; - }>, - ) => { - if (isApprover) { - tools = target || []; - } - }; - - const operationNeedPermission = { - [TaskStatus.EXECUTION_ABNORMAL]: { - operations: [buttonConfig.stopBtn, buttonConfig.againBtn], - }, - [TaskStatus.EXECUTING]: { - operations: [buttonConfig.stopBtn], - taskRules: [ - { - auth: true, - taskTypeLimit: [TaskType.STRUCTURE_COMPARISON], - operations: [buttonConfig.downloadSQLBtn, buttonConfig.structrueComparisonBySQL], - }, - ], - }, - [TaskStatus.APPROVING]: { - operations: [buttonConfig.stopBtn], - }, - [TaskStatus.EXECUTION_SUCCEEDED]: { - operations: [], - // taskRules 中的规则按顺序匹配,匹配上一个之后则应用该规则,不再继续匹配 - taskRules: [ - { - taskTypeLimit: [TaskType.EXPORT], - auth: settingStore.enableDataExport && isClient(), - operations: [buttonConfig.openLocalFolder], - }, - { - taskTypeLimit: [TaskType.EXPORT, TaskType.DATAMOCK, TaskType.EXPORT_RESULT_SET], - auth: settingStore.enableDataExport, - operations: [buttonConfig.downloadBtn], - }, - { - taskTypeLimit: [TaskType.ASYNC, TaskType.MULTIPLE_ASYNC], - auth: task?.rollbackable, - operations: [buttonConfig.rollbackBtn], - }, - { - taskTypeLimit: [TaskType.STRUCTURE_COMPARISON], - auth: true, - operations: [buttonConfig.downloadSQLBtn, buttonConfig.structrueComparisonBySQL], - }, - ], - }, - }; - const getSpecialExecuteBtn = () => { - const _executeBtn = { ...buttonConfig.executeBtn }; - if (task?.executionStrategy === TaskExecStrategy.TIMER) { - _executeBtn.disabled = true; - const executionTime = getLocalFormatDateTime(task?.executionTime); - - _executeBtn.tooltip = formatMessage( - { - id: 'odc.TaskManagePage.component.TaskTools.ScheduledExecutionTimeExecutiontime', - defaultMessage: '定时执行时间:{executionTime}', - }, - - { executionTime }, - ); - - //`定时执行时间:${executionTime}` - } - return _executeBtn; - }; - const viewResultBtn = { - key: 'viewResult', - text: formatMessage({ - id: 'src.component.Task.component.ActionBar.5218D741', - defaultMessage: '查询结果', - }), - isLoading: viewLoading, - action: viewResult, - type: 'button', - }; - if (isDetailModal) { - switch (status) { - case TaskStatus.REJECTED: - case TaskStatus.APPROVAL_EXPIRED: - case TaskStatus.WAIT_FOR_EXECUTION_EXPIRED: - case TaskStatus.EXECUTION_EXPIRED: - case TaskStatus.CREATED: - case TaskStatus.EXECUTION_FAILED: - case TaskStatus.ROLLBACK_FAILED: - case TaskStatus.ROLLBACK_SUCCEEDED: - case TaskStatus.CANCELLED: - case TaskStatus.PRE_CHECK_FAILED: - case TaskStatus.COMPLETED: { - addRetryButton(); - resetToolsForApprover(); - break; - } - case TaskStatus.EXECUTING: - case TaskStatus.EXECUTION_ABNORMAL: - case TaskStatus.EXECUTION_SUCCEEDED: { - addRetryButton(); - - if (haveOperationPermission) { - const taskRules = operationNeedPermission?.[status]?.taskRules; - taskRules?.some((rule) => { - return addOperations(rule || {}); - }); - tools.push(...operationNeedPermission[status].operations); - } - resetToolsForApprover(); - break; - } - case TaskStatus.WAIT_FOR_CONFIRM: - case TaskStatus.APPROVING: { - resetToolsForApprover([buttonConfig.rejectBtn, buttonConfig.approvalBtn]); - addRetryButton(); - if (haveOperationPermission) { - tools.push(...operationNeedPermission[status].operations); - } - break; - } - case TaskStatus.WAIT_FOR_EXECUTION: { - addRetryButton(); - - if (haveOperationPermission) { - const _executeBtn = getSpecialExecuteBtn(); - const tempTools = - task?.executionStrategy === TaskExecStrategy.AUTO - ? [buttonConfig.stopBtn] - : [buttonConfig.stopBtn, _executeBtn]; - tools.push(...tempTools); - } - resetToolsForApprover(); - break; - } - default: - } - - // 是否为数据库变更 - const isAsyncTask = task?.type === TaskType.ASYNC; - // 是否包含查询结果 - const hasQueryResult = result?.containQuery; - // 是否为管理员或者是发起者 - const hasPermission = - currentUserResourceRoles?.some((item) => [ProjectRole.OWNER].includes(item)) || isOwner; - - if (isAsyncTask && hasQueryResult && hasPermission) { - const allowDownloadResultSets = - setting.getSpaceConfigByKey('odc.task.databaseChange.allowDownloadResultSets') === - 'true'; - const allowShowResultSets = - setting.getSpaceConfigByKey('odc.task.databaseChange.allowShowResultSets') === 'true'; - if (settingStore.enableDataExport && allowDownloadResultSets) { - tools.unshift(buttonConfig.downloadViewResultBtn); - } - if (allowShowResultSets) { - tools.unshift(viewResultBtn); - } - } - } else { - tools = [buttonConfig.viewBtn]; - if (status === TaskStatus.WAIT_FOR_EXECUTION) { - if (haveOperationPermission) { - const _executeBtn = getSpecialExecuteBtn(); - task?.executionStrategy === TaskExecStrategy.AUTO - ? tools.push(buttonConfig.stopBtn) - : tools.push(_executeBtn, buttonConfig.stopBtn); - } - addRetryButton(); - } - } - tools = uniqueTools(tools); - return tools; - }; - - const getCycleTaskTools = (_task) => { - let tools = []; - if (!_task) { - return []; - } - const { status } = _task; - const buttongConfig = { - ...commonButtonConfig, - stopBtn: { - ...actionInfo.stopBtn, - action: async () => { - await handleTaskOperation({ - operationType: TaskOperationType.TERMINATE, - callback: () => { - setActiveBtnKey('stop'); - }, - }); - }, - }, - editBtn: { - ...actionInfo.editBtn, - action: _editCycleTask, - }, - reTryBtn: { - ...actionInfo.reTryBtn, - action: _retryCycleTask, - }, - disableBtn: { - ...actionInfo.disableBtn, - action: async () => { - await handleTaskOperation({ operationType: TaskOperationType.PAUSE }); - }, - }, - - /* 禁用Schedule下的Task for logical database change task */ - /* 很脏的逻辑, ued少一层导致的 */ - stopScheduleTaskBtn: { - ...actionInfo.stopScheduleTaskBtn, - action: _stopScheduleTask, - }, - enableBtn: { - ...actionInfo.enableBtn, - action: async () => { - await handleTaskOperation({ operationType: TaskOperationType.RESUME }); - }, - }, - approvalBtn: { - ...actionInfo.approvalBtn, - isPrimary: isDetailModal, - action: async () => { - _approvalTask(true); - }, - }, - deleteBtn: { - ...actionInfo.deleteBtn, - confirmText: formatMessage({ - id: 'src.component.Task.component.ActionBar.72AF1732', - defaultMessage: '你确定要删除这个任务吗?', - }), - action: _deleteTask, - }, - }; - const initTools = ({ view, retry }: { view?: boolean; retry?: boolean }) => { - if (view) { - tools = [buttongConfig.viewBtn]; - } - if (retry) { - setBtnByCreater(tools, buttongConfig.reTryBtn); - } - }; - - const operationNeedPermission = { - [TaskStatus.APPROVING]: { - auth: haveOperationPermission, - operations: [buttongConfig.stopBtn], - }, - [TaskStatus.PAUSE]: { - auth: haveOperationPermission, - operations: [buttongConfig.editBtn, buttongConfig.enableBtn, buttongConfig.stopBtn], - }, - [TaskStatus.CANCELLED]: { - auth: haveOperationPermission, - operations: [buttongConfig.stopBtn], - }, - [TaskStatus.REJECTED]: { auth: haveOperationPermission, operations: [] }, - [TaskStatus.APPROVAL_EXPIRED]: { - taskTypeLimit: SCHEDULE_TASKS, - auth: haveOperationPermission, - operations: [buttongConfig.deleteBtn], - }, - [TaskStatus.TERMINATED]: { - taskTypeLimit: SCHEDULE_TASKS, - auth: haveOperationPermission, - operations: [buttongConfig.deleteBtn], - }, - [TaskStatus.COMPLETED]: { - taskTypeLimit: SCHEDULE_TASKS, - auth: haveOperationPermission, - operations: [buttongConfig.deleteBtn], - }, - [TaskStatus.ENABLED]: { - auth: - haveOperationPermission && - !( - JOB_SCHEDULE_TASKS.includes(task?.type) && - (task as ICycleTaskRecord)?.triggerConfig?.triggerStrategy === - TaskExecStrategy.START_NOW - ), - operations: [buttongConfig.disableBtn, buttongConfig.stopBtn, buttongConfig.editBtn], - }, - }; - - const operationNeedApprover = { - [TaskStatus.APPROVING]: [buttongConfig.approvalBtn, buttongConfig.rejectBtn], - }; - - const addOperations = ({ taskTypeLimit, auth, operations }: IAddOperationsParams) => { - const hasOperations = Boolean(operations?.length); - const useableTaskType = !taskTypeLimit || taskTypeLimit?.includes(task?.type); - if (auth && hasOperations && useableTaskType) { - tools.push(...operations); - } - }; - const enableRetry = isOwner; - initTools({ view: true, retry: enableRetry }); - addOperations(operationNeedPermission?.[status] || {}); - - switch (status) { - case TaskStatus.APPROVING: { - addOperations({ auth: isApprover, operations: operationNeedApprover[status] }); - break; - } - case TaskStatus.REJECTED: { - setBtnByCreater(tools, buttongConfig.reTryBtn); - tools = [buttongConfig.viewBtn]; - setBtnByCreater(tools, buttongConfig.reTryBtn); - break; - } - case TaskStatus.ENABLED: { - if (haveOperationPermission && isLogicalDbChangeTask(task?.type)) { - tools = [buttongConfig.viewBtn, buttongConfig.editBtn, buttongConfig.reTryBtn]; - } - break; - } - default: - break; - } - - if (isDetailModal) { - tools = tools.filter((item) => !['view', 'delete'].includes(item.key)); - } else { - tools = tools.filter((item) => ['view', 'delete'].includes(item.key)); - } - - // sql 计划 & 数据归档 & 数据清理 支持编辑 - if (!SCHEDULE_TASKS.includes(task?.type)) { - tools = tools.filter((item) => item.key !== 'edit'); - } - if ((taskList?.[0]?.status as any) === SubTaskStatus.RUNNING) { - tools = [...tools, buttongConfig.stopScheduleTaskBtn]; - } - tools = uniqueTools(tools); - return tools; - }; - - const getTools = (task) => { - return isCycleTask(task?.type) ? getCycleTaskTools(task) : getTaskTools(task); - }; - - const btnTools = !isDetailModal - ? getTools(task).filter((item) => item?.type === 'button') - : getTools(task); - - const renderTool = (tool, index) => { - const ActionButton = isDetailModal ? Action.Button : Action.Link; - const disabled = - activeBtnKey === tool?.key || tool?.disabled || delTaskList?.includes(task.id); - if (tool.confirmText) { - return ( - - - {tool.text} - - - ); - } - - if (tool.download) { - return ( - - {tool.text} - - ); - } - - return ( - - - {tool.text} - - - ); - }; - - return ( - <> - - {btnTools?.map((tool, index) => { - return renderTool(tool, index); - })} - - {isDetailModal && ( - )?.parameters?.generateRollbackPlan && - !!result?.rollbackPlanResult?.resultFileDownloadUrl - } - onOk={confirmRollback} - onCancel={handleCloseRollback} - /> - )} - - ); - }), -); - -export default ActionBar; diff --git a/src/component/Task/component/ActionBar/type.ts b/src/component/Task/component/ActionBar/type.ts deleted file mode 100644 index e62511d42..000000000 --- a/src/component/Task/component/ActionBar/type.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { TaskType } from '@/d.ts'; - -export interface IAddOperationsParams { - taskTypeLimit?: TaskType[]; - auth: boolean; - operations: { - key: string; - text: any; - action: () => Promise; - type: string; - }[]; -} diff --git a/src/component/Task/component/ApprovalModal/index.less b/src/component/Task/component/ApprovalModal/index.less index 4ff8d0d6e..db813a512 100644 --- a/src/component/Task/component/ApprovalModal/index.less +++ b/src/component/Task/component/ApprovalModal/index.less @@ -1,5 +1,5 @@ .approvalModal { - .block:global(.ant-space) { + :global(.ant-space) { width: 100%; } :global { diff --git a/src/component/Task/component/ApprovalModal/index.tsx b/src/component/Task/component/ApprovalModal/index.tsx index ed964066c..962765ab2 100644 --- a/src/component/Task/component/ApprovalModal/index.tsx +++ b/src/component/Task/component/ApprovalModal/index.tsx @@ -31,11 +31,12 @@ interface IProps { approvalStatus: boolean; onCancel: () => void; onReload: () => void; + zIndex?: number; } const ApprovalModal: React.FC = inject('taskStore')( observer((props) => { - const { taskStore, id, visible, approvalStatus, onCancel } = props; + const { taskStore, id, visible, approvalStatus, onCancel, zIndex = 1001 } = props; const [confirmLoading, setConfirmLoading] = useState(false); const formRef = useRef(null); @@ -107,7 +108,7 @@ const ApprovalModal: React.FC = inject('taskStore')( confirmLoading={confirmLoading} onOk={onSubmit} onCancel={handleCancel} - zIndex={1001} + zIndex={zIndex} > diff --git a/src/component/Task/component/AsyncTaskOperationButton/SubmitTripartiteTaskButton.tsx b/src/component/Task/component/AsyncTaskOperationButton/SubmitTripartiteTaskButton.tsx index 1ff6ce036..b429480c8 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/SubmitTripartiteTaskButton.tsx +++ b/src/component/Task/component/AsyncTaskOperationButton/SubmitTripartiteTaskButton.tsx @@ -12,10 +12,12 @@ import { cancelFlowInstance, getBatchCancelResult, getBatchCancelLog, +} from '@/common/network/task'; +import { batchTerminateScheduleAndTask, getTerminateScheduleResult, getTerminateScheduleLog, -} from '@/common/network/task'; +} from '@/common/network/schedule'; import { LoadingOutlined } from '@ant-design/icons'; import type { AsyncTaskModalConfig } from './hooks/useTaskTable'; import { useDebounceFn } from 'ahooks'; diff --git a/src/component/Task/component/AsyncTaskOperationButton/helper.tsx b/src/component/Task/component/AsyncTaskOperationButton/helper.tsx index 8feeef80a..90e46ff91 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/helper.tsx +++ b/src/component/Task/component/AsyncTaskOperationButton/helper.tsx @@ -19,8 +19,13 @@ import RiskLevelLabel from '@/component/RiskLevelLabel'; import Icon, { ExclamationCircleFilled } from '@ant-design/icons'; import { ConnectTypeText } from '@/constant/label'; import { formatMessage } from '@/util/intl'; -import { statusThatCanBeExport, statusThatCanBeTerminate } from '../TaskTable/useTaskSelection'; +import { + taskStatusThatCanBeTerminate, + scheduleStatusThatCanBeExport, + SchedulestatusThatCanBeTerminate, +} from '@/constant/triangularization'; import { TaskTypeMap } from '../TaskTable/const'; +import { ScheduleStatus, ScheduleType } from '@/d.ts/schedule'; export const DatabasePopover: React.FC<{ connection: Partial; @@ -308,13 +313,17 @@ export const getExportConfig: ( export const getTerminateConfig: ( datasource, -) => Omit = (datasource) => { + isSchedule?: boolean, +) => Omit = ( + datasource, + isSchedule = false, +) => { return { asyncTaskType: [ - TaskType.SQL_PLAN, - TaskType.PARTITION_PLAN, - TaskType.DATA_ARCHIVE, - TaskType.DATA_DELETE, + ScheduleType.SQL_PLAN, + ScheduleType.PARTITION_PLAN, + ScheduleType.DATA_ARCHIVE, + ScheduleType.DATA_DELETE, ]?.includes(datasource?.[0]?.type) ? AsyncTaskType.terminateSchedule : AsyncTaskType.terminateTask, @@ -448,35 +457,43 @@ export const getTerminateConfig: ( confirmButtonType: 'danger', needRiskConfirm: true, needSelectSpace: false, - checkStatus: checkIsTaskListCanBeTerminated, - checkStatusFailed: formatMessage({ - id: 'src.component.Task.component.AsyncTaskOperationButton.E5D14CDC', - defaultMessage: - '请选择运行中的任务(包括待执行、排队中、执行中)和正常调度的定时任务(包括已创建、已启用、已禁用)', - }), + checkStatus: isSchedule + ? checkIsScheduleTaskListCanBeTerminated + : checkIsTaskListCanBeTerminated, + checkStatusFailed: isSchedule + ? '请选择运行中的作业' + : formatMessage({ + id: 'src.component.Task.component.AsyncTaskOperationButton.E5D14CDC', + defaultMessage: + '请选择运行中的任务(包括待执行、排队中、执行中)和正常调度的定时任务(包括已创建、已启用、已禁用)', + }), }; }; // 是否为导出任务支持的周期任务 -export const isScheduleMigrateTask = (taskType: TaskType) => { +export const isScheduleMigrateTask = (scheduleType: ScheduleType) => { return [ - TaskType.DATA_ARCHIVE, - TaskType.DATA_DELETE, - TaskType.PARTITION_PLAN, - TaskType.SQL_PLAN, - ]?.includes(taskType); + ScheduleType.DATA_ARCHIVE, + ScheduleType.DATA_DELETE, + ScheduleType.PARTITION_PLAN, + ScheduleType.SQL_PLAN, + ]?.includes(scheduleType); }; // 是否是在正常调度状态的任务(已创建, 已启用, 已禁用) export const checkIsScheduleTaskListCanBeExported = (taskStatus: TaskStatus) => { - return statusThatCanBeExport?.includes(taskStatus); + return scheduleStatusThatCanBeExport?.includes(taskStatus); }; // 是否是能终止的任务状态 export const checkIsTaskListCanBeTerminated = (taskStatus: TaskStatus) => { - return statusThatCanBeTerminate?.includes(taskStatus); + return taskStatusThatCanBeTerminate?.includes(taskStatus); }; +// 是否是能终止的作业状态 +const checkIsScheduleTaskListCanBeTerminated = (scheduleStatus: ScheduleStatus) => { + return SchedulestatusThatCanBeTerminate?.includes(scheduleStatus); +}; /** * 从 URL 中提取 filename 参数的值 * @param {string} url - 完整的 URL 字符串 diff --git a/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts b/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts index 67de61f97..201a89d55 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts +++ b/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts @@ -2,6 +2,7 @@ import { useState } from 'react'; import type { AsyncTaskType, ISwitchOdcTaskListResponse } from '@/d.ts/migrateTask'; import { UnfinishedScheduleListType } from '@/d.ts/migrateTask'; import { TaskRecord, TaskRecordParameters, TaskStatus } from '@/d.ts'; +import { ScheduleStatus } from '@/d.ts/schedule'; export interface AsyncTaskModalConfig { asyncTaskType: AsyncTaskType; @@ -17,7 +18,7 @@ export interface AsyncTaskModalConfig { modalTitle: string; modalExtra: (count: number, ids?: number[]) => React.ReactNode; - checkStatus: (status: TaskStatus) => boolean; + checkStatus: (status: TaskStatus | ScheduleStatus) => boolean; checkStatusFailed: string; onReload: () => void; diff --git a/src/component/Task/component/AsyncTaskOperationButton/index.tsx b/src/component/Task/component/AsyncTaskOperationButton/index.tsx index 81ff73427..7dd616938 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/index.tsx +++ b/src/component/Task/component/AsyncTaskOperationButton/index.tsx @@ -10,11 +10,12 @@ import { ScheduleExportListView } from '@/d.ts/migrateTask'; import { TaskType } from '@/d.ts'; import datasourceStatus from '@/store/datasourceStatus'; import { isScheduleMigrateTask } from './helper'; +import { ScheduleType } from '@/d.ts/schedule'; export interface IAsyncTaskOperationConfig extends AsyncTaskModalConfig { buttonText: string; buttonDisabledText: string; - buttonType: 'primary' | 'default'; + buttonType: 'primary' | 'default' | 'text'; } export function AsyncTaskOperationButton(props: IAsyncTaskOperationConfig) { @@ -44,14 +45,14 @@ export function AsyncTaskOperationButton(props: IAsyncTaskOperationConfig) { if (!props.dataSource?.length) { return; } - const scheduleType = props?.dataSource?.[0]?.type; + const scheduleType = props?.dataSource?.[0]?.type as unknown as ScheduleType; if (isScheduleMigrateTask(scheduleType)) { const res = await getExportListView({ ids: props?.dataSource?.map((i) => i?.id), scheduleType, }); setTaskList( - scheduleType === TaskType.PARTITION_PLAN + scheduleType === ScheduleType.PARTITION_PLAN ? res?.map((i) => { return { ...i, diff --git a/src/component/Task/component/CommonDetailModal/ChangeDetail.tsx b/src/component/Task/component/CommonDetailModal/ChangeDetail.tsx deleted file mode 100644 index 05ee00d18..000000000 --- a/src/component/Task/component/CommonDetailModal/ChangeDetail.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { formatMessage } from '@/util/intl'; -import React, { useEffect, useState } from 'react'; -import { Modal, Descriptions, Spin } from 'antd'; -import { getOperationDetail } from '@/common/network/task'; -import { Operation } from '@/d.ts'; -import DiffEditor from '@/component/MonacoEditor/DiffEditor'; -import styles from './index.less'; -import { useRequest } from 'ahooks'; - -interface ChangeDetailProps { - scheduleId: number; - scheduleChangeLogId: number; - visible: boolean; - onClose: () => void; -} - -const ChangeDetail: React.FC = (props) => { - const { visible, onClose, scheduleId, scheduleChangeLogId } = props; - const [data, setData] = useState(); - - useEffect(() => { - if (scheduleId && scheduleChangeLogId) { - loadData(); - } - }, [scheduleId, scheduleChangeLogId]); - - const { run: fetchOperationDetail, loading } = useRequest(getOperationDetail, { - manual: true, - }); - - const loadData = async () => { - setData(null); - const data = await fetchOperationDetail(scheduleId, scheduleChangeLogId); - setData(data); - }; - - return ( - - -
- - - {formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.AF00DE0E', - defaultMessage: '变更前:', - })} - - - {formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.1CF173E7', - defaultMessage: '变更后:', - })} - - - {data && ( -
- -
- )} -
-
-
- ); -}; - -export default ChangeDetail; diff --git a/src/component/Task/component/CommonDetailModal/FlowModal.tsx b/src/component/Task/component/CommonDetailModal/FlowModal.tsx deleted file mode 100644 index 8c3441bb6..000000000 --- a/src/component/Task/component/CommonDetailModal/FlowModal.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 { getTaskDetail } from '@/common/network/task'; -import { TaskOperationType } from '@/d.ts'; -import { formatMessage } from '@/util/intl'; -import { Button, Drawer, Space, Spin } from 'antd'; -import React, { useEffect, useState } from 'react'; -import ApprovalModal from '../ApprovalModal'; -import styles from './index.less'; -import TaskFlow from './TaskFlow'; -import { operationTypeMap } from './TaskOperationRecord'; -interface IProps { - id: number; - operationType: TaskOperationType; - visible: boolean; - onClose: () => void; -} -const FlowModal: React.FC = function (props) { - const { visible, id, operationType, onClose } = props; - const [loading, setLoading] = useState(false); - const [task, setTask] = useState(null); - const [approvalVisible, setApprovalVisible] = useState(false); - const [approvalStatus, setApprovalStatus] = useState(false); - const getTask = async function (id) { - setLoading(true); - const data = await getTaskDetail(id); - setLoading(false); - setTask(data); - }; - useEffect(() => { - if (id) { - getTask(id); - } - }, [id]); - const handleApprovalVisible = (approvalStatus: boolean = false, visible: boolean = false) => { - setApprovalVisible(visible); - setApprovalStatus(approvalStatus); - }; - return ( - - - -
- ) - } - zIndex={1000} - > - - - { - formatMessage({ - id: 'odc.component.CommonTaskDetailModal.FlowModal.ActionEvents', - defaultMessage: '操作事件:', - }) /*操作事件:*/ - } - - {operationTypeMap?.[operationType]} - - {task && } - { - getTask(id); - }} - onCancel={() => { - handleApprovalVisible(false); - }} - /> -
- ); -}; -export default FlowModal; diff --git a/src/component/Task/component/CommonDetailModal/LogModal.tsx b/src/component/Task/component/CommonDetailModal/LogModal.tsx deleted file mode 100644 index 25f07a85d..000000000 --- a/src/component/Task/component/CommonDetailModal/LogModal.tsx +++ /dev/null @@ -1,113 +0,0 @@ -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 { getCycleTaskLog, getDownloadUrl } from '@/common/network/task'; -import type { ILog } from '@/component/Task/component/Log'; -import TaskLog from '@/component/Task/component/Log'; -import { CommonTaskLogType, SubTaskStatus } from '@/d.ts'; -import { useRequest } from 'ahooks'; -import { Drawer } from 'antd'; -import React, { useEffect, useState } from 'react'; -interface IProps { - scheduleId: number; - recordId: number; - visible: boolean; - onClose: () => void; - status?: SubTaskStatus; -} -const LogModal: React.FC = function (props) { - const { visible, scheduleId, recordId, onClose, status } = props; - const [logType, setLogType] = useState(CommonTaskLogType.ALL); - const [loading, setLoading] = useState(false); - const [log, setLog] = useState(null); - const [downloadUrl, setDownloadUrl] = useState(undefined); - const { run: getLog, cancel } = useRequest( - async (scheduleId, recordId, logType) => { - if (scheduleId && recordId && logType) { - const res = await getCycleTaskLog(scheduleId, recordId, logType); - setLog({ - ...log, - [logType]: res, - }); - setLoading(false); - if ( - status && - [SubTaskStatus.CANCELED, SubTaskStatus.FAILED, SubTaskStatus.DONE].includes(status) - ) { - cancel(); - } - } - }, - { - pollingInterval: 3000, - }, - ); - - const { run: getLogDownLoadUrl } = useRequest(async (scheduleId, recordId) => { - if (scheduleId && recordId) { - const res = await getDownloadUrl(scheduleId, recordId); - if (!!res) { - setDownloadUrl(res); - } - } - }); - - const handleLogTypeChange = (type: CommonTaskLogType) => { - setLogType(type); - }; - useEffect(() => { - if (visible) { - getLog(scheduleId, recordId, logType); - getLogDownLoadUrl(scheduleId, recordId); - } - }, [scheduleId, recordId, visible, logType]); - - useEffect(() => { - if (visible) { - setLoading(true); - } - return () => { - if (visible) { - setLogType(CommonTaskLogType.ALL); - cancel(); - } - }; - }, [visible]); - return ( - - - - ); -}; -export default LogModal; diff --git a/src/component/Task/component/CommonDetailModal/TaskExecuteRecord.tsx b/src/component/Task/component/CommonDetailModal/TaskExecuteRecord.tsx deleted file mode 100644 index 8a5cb60de..000000000 --- a/src/component/Task/component/CommonDetailModal/TaskExecuteRecord.tsx +++ /dev/null @@ -1,567 +0,0 @@ -/* - * 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 { getDataSourceStyleByConnectType } from '@/common/datasource'; -import { skipPhysicalSqlExecute, stopPhysicalSqlExecute } from '@/common/network/logicalDatabase'; -import { getScheduleTaskDetail } from '@/common/network/task'; -import CommonTable from '@/component/CommonTable'; -import { CommonTableMode, ITableLoadOptions } from '@/component/CommonTable/interface'; -import SearchFilter from '@/component/SearchFilter'; -import StatusLabel, { - logicDBChangeTaskStatus, - status, - subTaskStatus, -} from '@/component/Task/component/Status'; -import DetailModal from '@/component/Task/modals/DetailModals'; -import { - IAsyncTaskParams, - IResponseData, - SubTaskStatus, - SubTaskType, - TaskRecord, - TaskRecordParameters, - TaskType, - CycleTaskDetail, - IDataArchiveJobParameters, - IDataClearJobParameters, - ISqlPlayJobParameters, -} from '@/d.ts'; - -import { ISchemaChangeRecord, SchemaChangeRecordStatus } from '@/d.ts/logicalDatabase'; -import { formatMessage } from '@/util/intl'; -import { getFormatDateTime } from '@/util/utils'; -import Icon, { FilterOutlined, SearchOutlined } from '@ant-design/icons'; -import { Space, Typography, message } from 'antd'; -import React, { useEffect, useRef, useState } from 'react'; -import { isLogicalDbChangeTask } from '@/component/Task/helper'; -import ExcecuteDetailModal from './ExcecuteDetailModal'; -import styles from './index.less'; -import LogModal from './LogModal'; -import TaskProgressModal from './TaskExecuteModal'; -import TaskTools from './TaskTools'; - -const { Link } = Typography; - -const TaskLabelMap = { - [TaskType.DATA_ARCHIVE]: { - [SubTaskType.DATA_ARCHIVE]: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.DataArchiving', - defaultMessage: '数据归档', - }), //数据归档 - [SubTaskType.DATA_ARCHIVE_ROLLBACK]: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.Rollback', - defaultMessage: '回滚', - }), //回滚 - [SubTaskType.DATA_ARCHIVE_DELETE]: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.SourceTableCleanup', - defaultMessage: '源表清理', - }), //源表清理 - }, - [TaskType.DATA_DELETE]: { - [SubTaskType.DATA_DELETE]: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.DataCleansing', - defaultMessage: '数据清理', - }), //数据清理 - }, - [TaskType.SQL_PLAN]: { - [SubTaskType.ASYNC]: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.DatabaseChanges', - defaultMessage: '数据库变更', - }), //数据库变更 - }, -}; - -const getStatusFilters = (isSubTask) => { - const statusMap = isSubTask ? subTaskStatus : status; - return Object.keys(statusMap).map((key) => { - return { - text: statusMap?.[key].text, - value: key, - }; - }); -}; - -const getJobFilter = (taskType: TaskType) => { - return Object.keys(TaskLabelMap[taskType])?.map((key) => ({ - text: TaskLabelMap[taskType][key], - value: key, - })); -}; - -const getLogicalDatabaseAsyncColumns = (params: { - handleLogicalDatabaseAsyncModalOpen: (taskId: number) => void; - handleLogicalDatabaseTaskStop: (taskId: number) => void; - handleLogicalDatabaseTaskSkip: (taskId: number) => void; -}) => { - return [ - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.7E35E39B', - defaultMessage: '执行数据库', - }), - key: 'database', - dataIndex: 'database', - ellipsis: { - showTitle: true, - }, - filterDropdown: (props) => { - return ( - - ); - }, - filterIcon: (filtered) => ( - - ), - - onFilter: (value, record) => { - return record?.database?.name?.includes(value); - }, - render: (_, record) => { - const icon = getDataSourceStyleByConnectType(record?.database?.dataSource?.type); - return ( - - - - -
{record?.database?.name}
-
-
- ); - }, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.B38FABC4', - defaultMessage: '数据源', - }), - key: 'datasource', - dataIndex: 'datasource', - render: (value, record) => { - return record?.database?.dataSource?.name; - }, - filterDropdown: (props) => { - return ( - - ); - }, - filterIcon: (filtered) => ( - - ), - - onFilter: (value, record) => { - return record?.dataSource?.name?.includes(value); - }, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.D5F9DCA0', - defaultMessage: '执行状态', - }), - key: 'status', - dataIndex: 'status', - render: (value, row: ISchemaChangeRecord) => { - return ( - - {logicDBChangeTaskStatus[value]?.icon} - - {logicDBChangeTaskStatus[value]?.text}({row?.completedSqlCount}/{row?.totalSqlCount}) - - - ); - }, - onFilter: (value, record) => { - return value == record?.status; - }, - filters: Object.entries(SchemaChangeRecordStatus).map(([key, value]) => { - return { - text: logicDBChangeTaskStatus[key]?.text, - value: key, - }; - }), - }, - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.13DCD7AB', - defaultMessage: '操作', - }), - key: 'operation', - render: (value, record: ISchemaChangeRecord) => { - return ( - - params?.handleLogicalDatabaseAsyncModalOpen(record?.id)}> - {formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.178F11D7', - defaultMessage: '查看', - })} - - {record?.status === SchemaChangeRecordStatus.RUNNING && ( - params?.handleLogicalDatabaseTaskStop(record?.id)}> - {formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.7EF67970', - defaultMessage: '终止', - })} - - )} - - {[ - SchemaChangeRecordStatus.FAILED, - SchemaChangeRecordStatus.TERMINATED, - SchemaChangeRecordStatus.TERMINATE_FAILED, - ]?.includes(record?.status) && ( - params?.handleLogicalDatabaseTaskSkip(record?.id)}> - {formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.88502ED7', - defaultMessage: '跳过', - })} - - )} - - ); - }, - }, - ]; -}; - -const getConnectionColumns = (params: { - task: CycleTaskDetail< - IDataArchiveJobParameters | IDataClearJobParameters | ISqlPlayJobParameters - >; - showLog: boolean; - onReloadList: () => void; - onDetailVisible: (task: TaskRecord, visible: boolean) => void; - onLogVisible: (recordId: number, visible: boolean, status: SubTaskStatus) => void; - onExcecuteDetailVisible: (recordId: number, visible: boolean) => void; - handleLogicalDatabaseTaskStop; - handleLogicalDatabaseTaskSkip; - handleLogicalDatabaseAsyncModalOpen; -}) => { - const { - task, - showLog, - onReloadList, - onDetailVisible, - onLogVisible, - onExcecuteDetailVisible, - handleLogicalDatabaseTaskStop, - handleLogicalDatabaseTaskSkip, - handleLogicalDatabaseAsyncModalOpen, - } = params; - const { id: taskId, type: taskType } = task; - let showRollback = true; - if (task.type === TaskType.DATA_ARCHIVE) { - showRollback = !(task as CycleTaskDetail).jobParameters - .deleteTemporaryTable; - } - if (isLogicalDbChangeTask(taskType)) { - return getLogicalDatabaseAsyncColumns({ - handleLogicalDatabaseTaskStop, - handleLogicalDatabaseTaskSkip, - handleLogicalDatabaseAsyncModalOpen, - }); - } - const jobFilter = getJobFilter(taskType); - const isSqlPlan = taskType === TaskType.SQL_PLAN; - const statusFilters = getStatusFilters(!isSqlPlan); - return [ - { - dataIndex: 'id', - title: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.TaskNumber', - defaultMessage: '任务编号', - }), //任务编号 - ellipsis: true, - width: 80, - }, - - { - dataIndex: 'jobGroup', - title: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.TaskType', - defaultMessage: '任务类型', - }), //任务类型 - ellipsis: true, - filterIcon: , - filters: jobFilter, - onFilter: (value: string, record) => { - return isSqlPlan ? value === SubTaskType.ASYNC : value === record.jobGroup; - }, - render: (jobGroup) => { - return TaskLabelMap[taskType][isSqlPlan ? SubTaskType.ASYNC : jobGroup]; - }, - }, - - { - dataIndex: 'createTime', - title: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.CreationTime', - defaultMessage: '创建时间', - }), //创建时间 - ellipsis: true, - width: 150, - render: (createTime) => getFormatDateTime(createTime), - }, - - { - dataIndex: 'status', - title: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.TaskStatus', - defaultMessage: '任务状态', - }), //任务状态 - ellipsis: true, - width: 120, - filters: statusFilters, - filterIcon: , - onFilter: (value: string, record) => { - return value === record.status; - }, - render: (status, record) => { - return ( - - ); - }, - }, - - { - dataIndex: 'action', - title: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskExecuteRecord.Operation', - defaultMessage: '操作', - }), //操作 - ellipsis: true, - width: 210, - render: (_, record) => { - return ( - - ); - }, - }, - ]; -}; - -interface IProps { - task: any; - subTasks: IResponseData>; - onReload: (args?: ITableLoadOptions) => void; -} - -const TaskExecuteRecord: React.FC = (props) => { - const { task, subTasks: flowList, onReload } = props; - const [subTasks, setSubTasks] = useState([]); - const [detailId, setDetailId] = useState(null); - const [detailVisible, setDetailVisible] = useState(false); - const [logVisible, setLogVisible] = useState(false); - const [status, setStatus] = useState(null); - const [excecuteDetailVisible, setExcecuteDetailVisible] = useState(false); - const tableRef = useRef(); - const taskId = task?.id; - const showLog = [TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE]?.includes(task?.type); - const [modalOpen, setModalOpen] = useState(false); - - const handleDetailVisible = ( - task: TaskRecord, - visible: boolean = false, - ) => { - setDetailId(task?.id); - setDetailVisible(visible); - }; - /* 逻辑库变更是永远只有一个flow的调度任务, 逻辑库交互上少了flow列表这一层的话需要自己拿第一个结果的id查 */ - useEffect(() => { - if (flowList?.contents?.length && isLogicalDbChangeTask(task?.type)) { - const taskId = task?.id; - const flowId = flowList?.contents?.[0]?.id; - getSubTasks(taskId, flowId); - } - }, [flowList]); - - const getSubTasks = async (taskId: number, jobId: number) => { - const res = await getScheduleTaskDetail(taskId, jobId); - setSubTasks(res?.executionDetails); - }; - - const handleLogVisible = ( - recordId: number, - visible: boolean = false, - status: SubTaskStatus = null, - ) => { - setLogVisible(visible); - setDetailId(recordId); - setStatus(status); - }; - - const handleCloseLog = () => { - handleLogVisible(null); - }; - - const handleExcecuteDetailVisible = (recordId: number, visible: boolean = false) => { - setExcecuteDetailVisible(visible); - setDetailId(recordId); - }; - const handleCloseExcecuteDetail = () => { - handleExcecuteDetailVisible(null); - }; - - const handleLoad = async (args?: ITableLoadOptions) => { - onReload(args); - }; - - const handleLogicalDatabaseAsyncModalOpen = (detailId: number) => { - setModalOpen(true); - setDetailId(detailId); - }; - - const handleLogicalDatabaseTaskStop = async (detailId: number) => { - const res = await stopPhysicalSqlExecute(flowList?.contents?.[0]?.id, detailId); - if (res) { - message.success( - formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.1F689C8D', - defaultMessage: '正在尝试终止', - }), - ); - } else { - message.warning( - formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.30E204EE', - defaultMessage: '当前任务状态不支持终止', - }), - ); - } - onReload?.(); - }; - - const handleLogicalDatabaseTaskSkip = async (detailId: number) => { - const res = await skipPhysicalSqlExecute(flowList?.contents?.[0]?.id, detailId); - if (res) { - message.success( - formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.B0CD0DE9', - defaultMessage: '正在尝试跳过', - }), - ); - } else { - message.warning( - formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.6C8E11EA', - defaultMessage: '当前任务状态不支持跳过', - }), - ); - } - onReload?.(); - }; - - return ( - <> - - - - - - - - - - - ); -}; - -export default TaskExecuteRecord; diff --git a/src/component/Task/component/CommonDetailModal/TaskOperationRecord.tsx b/src/component/Task/component/CommonDetailModal/TaskOperationRecord.tsx deleted file mode 100644 index e78f389bf..000000000 --- a/src/component/Task/component/CommonDetailModal/TaskOperationRecord.tsx +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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 { getTaskDetail } from '@/common/network/task'; -import Action from '@/component/Action'; -import DisplayTable from '@/component/DisplayTable'; -import { status } from '@/component/Task/component/Status'; -import { TaskOperationType, TaskRecord, TaskRecordParameters, Operation, TaskType } from '@/d.ts'; -import { formatMessage } from '@/util/intl'; -import { getFormatDateTime } from '@/util/utils'; -import { FilterOutlined } from '@ant-design/icons'; -import React, { useEffect, useState } from 'react'; -import FlowModal from './FlowModal'; -import ChangeDetail from './ChangeDetail'; -import StatusItem from './status'; -import styles from './index.less'; - -const statusFilters = Object.keys(status).map((key) => { - return { - text: status?.[key].text, - value: key, - }; -}); - -export const operationTypeMap = { - [TaskOperationType.CREATE]: formatMessage({ - id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.CreateATask', - defaultMessage: '创建任务', - }), //创建任务 - [TaskOperationType.UPDATE]: formatMessage({ - id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.EditTask', - defaultMessage: '编辑任务', - }), //编辑任务 - [TaskOperationType.PAUSE]: formatMessage({ - id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.DisableATask', - defaultMessage: '停用任务', - }), //停用任务 - [TaskOperationType.TERMINATE]: formatMessage({ - id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.TerminateATask', - defaultMessage: '终止任务', - }), //终止任务 - [TaskOperationType.RESUME]: formatMessage({ - id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.EnableTasks', - defaultMessage: '启用任务', - }), //启用任务 -}; - -const getConnectionColumns = (params: { - onOpenDetail: (task: TaskRecord | Operation, visible: boolean) => void; - onOpenChangeDetail: (task: Operation, visible: boolean) => void; - taskType: TaskType; -}) => { - return [ - { - dataIndex: 'id', - title: formatMessage({ - id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.EventOperations', - defaultMessage: '事件操作', - }), //事件操作 - ellipsis: true, - width: 140, - render: (id, record) => { - return {operationTypeMap?.[record.type]}; - }, - }, - - { - dataIndex: 'createTime', - title: formatMessage({ - id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.OperationTime', - defaultMessage: '操作时间', - }), //操作时间 - ellipsis: true, - width: 180, - render: (createTime) => getFormatDateTime(createTime), - }, - - { - dataIndex: 'status', - title: formatMessage({ - id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.ApprovalStatus', - defaultMessage: '审批状态', - }), //审批状态 - ellipsis: true, - width: 140, - filters: statusFilters, - filterIcon: , - onFilter: (value: string, record) => { - return value === record.status; - }, - render: (status, record) => { - return ; - }, - }, - - { - dataIndex: 'action', - title: formatMessage({ - id: 'odc.component.CommonTaskDetailModal.TaskOperationRecord.Operation', - defaultMessage: '操作', - }), //操作 - ellipsis: true, - width: 92, - render: (_, record) => { - return ( - <> - {/* 无需审批的事件(例如个人空间内的一些工单)操作隐藏掉审批记录入口 */} - {record.flowInstanceId ? ( - { - params?.onOpenDetail(record, true); - }} - > - {formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.3D4F5474', - defaultMessage: '审批记录', - })} - - ) : undefined} - { - params?.onOpenChangeDetail(record, true); - }} - > - {formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.5C706BA6', - defaultMessage: '变更详情', - })} - - - ); - }, - }, - ]; -}; - -interface IProps { - opRecord: Operation[]; - onReload: () => void; - taskType: TaskType; -} - -const TaskOperationRecord: React.FC = (props) => { - const { opRecord, onReload, taskType } = props; - const [detailId, setDetailId] = useState(null); - const [subTask, setSubTask] = useState(null); - const [detailVisible, setDetailVisible] = useState(false); - const [changeDetailVisible, setChangeDetailVisible] = useState(false); - const [task, setTask] = useState(); - const { id, parameters } = subTask ?? {}; - - const handleDetailVisible = ( - task: Operation | TaskRecord, - visible: boolean = false, - ) => { - setDetailId((task as Operation)?.flowInstanceId); - setDetailVisible(visible); - }; - - const handleChangeDetailVisible = (task: Operation, visible: boolean = false) => { - setTask(task); - setChangeDetailVisible(visible); - }; - - const loadData = async () => { - const data = await getTaskDetail(detailId); - setSubTask(data); - }; - - useEffect(() => { - if (detailId) { - loadData(); - } - }, [detailId]); - - return ( - <> - - - { - handleDetailVisible(null); - onReload(); - }} - /> - - setChangeDetailVisible(null)} - /> - - ); -}; - -export default TaskOperationRecord; diff --git a/src/component/Task/component/CommonDetailModal/TaskProgress1.tsx b/src/component/Task/component/CommonDetailModal/TaskProgress1.tsx deleted file mode 100644 index 5819ab056..000000000 --- a/src/component/Task/component/CommonDetailModal/TaskProgress1.tsx +++ /dev/null @@ -1,584 +0,0 @@ -/* - * 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 { - getDataSourceModeConfigByConnectionMode, - getDataSourceStyleByConnectType, -} from '@/common/datasource'; -import { getSubTask, getTaskDetail, swapTableName } from '@/common/network/task'; -import Action from '@/component/Action'; -import DisplayTable from '@/component/DisplayTable'; -import RiskLevelLabel from '@/component/RiskLevelLabel'; -import { SQLContent } from '@/component/SQLContent'; -import StatusLabel from '@/component/Task/component/Status'; -import { - IMultipleAsyncTaskParams, - TaskDetail, - TaskPageType, - TaskRecordParameters, - TaskType, -} from '@/d.ts'; -import { TaskStore } from '@/store/task'; -import { formatMessage } from '@/util/intl'; -import { Button, Drawer, Popover, Space, message } from 'antd'; -import { getLocalFormatDateTime } from '@/util/utils'; -import Icon from '@ant-design/icons'; -import { useRequest } from 'ahooks'; -import { inject, observer } from 'mobx-react'; -import React, { useContext, useEffect, useState } from 'react'; -import { flatArray } from '@/util/utils'; -import { TaskDetailContext } from '@/component/Task/container/TaskDetailContext'; -import { SimpleTextItem } from '../SimpleTextItem'; -import styles from './index.less'; -const getColumns = (params: { - onOpenDetail: (id: number) => void; - onSwapTable: (id: number) => void; -}) => { - return [ - { - dataIndex: 'resultJson', - title: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskProgress.SourceTable', - defaultMessage: '源表', - }), - //源表 - ellipsis: true, - render: (resultJson) => { - return {JSON.parse(resultJson ?? '{}')?.originTableName}; - }, - }, - { - dataIndex: 'status', - title: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskProgress.ExecutionStatus', - defaultMessage: '执行状态', - }), - //执行状态 - ellipsis: true, - width: 140, - render: (status, record) => { - return ( - - ); - }, - }, - { - dataIndex: 'action', - title: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskProgress.Operation', - defaultMessage: '操作', - }), - //操作 - ellipsis: true, - width: 120, - render: (_, record) => { - const resultJson = JSON.parse(record?.resultJson); - return ( - <> - { - params?.onOpenDetail(record?.id); - }} - > - { - formatMessage({ - id: 'odc.component.CommonDetailModal.TaskProgress.View', - defaultMessage: '查看', - }) /*查看*/ - } - - {resultJson?.manualSwapTableEnabled && ( - { - params?.onSwapTable(record?.id); - }} - > - { - formatMessage({ - id: 'odc.src.component.Task.component.CommonDetailModal.WatchNameSwitch', - defaultMessage: '\n 表名切换\n ', - }) /* - 表名切换 - */ - } - - )} - - ); - }, - }, - ]; -}; -const getMultipleAsyncColumns = ({ onOpenDetail }: { onOpenDetail: (taskId: number) => void }) => { - return [ - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.FC1C254D', - defaultMessage: '执行顺序', - }), - dataIndex: 'nodeIndex', - width: 100, - render: (nodeIndex) => nodeIndex + 1, - onCell: (record, index) => { - return { - rowSpan: record?.needMerge ? record?.rowSpan : 0, - }; - }, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.9A136568', - defaultMessage: '数据库', - }), - dataIndex: 'database', - width: 200, - ellipsis: { - showTitle: true, - }, - render: (_, record) => { - const icon = getDataSourceStyleByConnectType(record?.database?.dataSource?.type); - return ( - - - - - - -
{record?.database?.name}
-
- {record?.database?.dataSource?.name} -
-
- - } - > - - - - - - -
{record?.database?.name}
-
- {record?.database?.dataSource?.name} -
-
-
-
- ); - }, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.0DAC1A06', - defaultMessage: '开始时间', - }), - dataIndex: 'createTime', - width: 178, - render: (_, record) => ( -
- {record?.flowInstanceDetailResp?.createTime - ? getLocalFormatDateTime(record?.flowInstanceDetailResp?.createTime) - : '-'} -
- ), - }, - { - dataIndex: 'status', - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.B46A5216', - defaultMessage: '执行状态', - }), - ellipsis: true, - width: 120, - render: (status, record) => { - return ( - - ); - }, - }, - { - dataIndex: 'action', - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.1DB56DDA', - defaultMessage: '操作', - }), - ellipsis: true, - width: 90, - render: (_, record) => { - return ( - <> - { - onOpenDetail(record?.flowInstanceDetailResp?.id); - }} - > - { - formatMessage({ - id: 'odc.component.CommonDetailModal.TaskProgress.View', - defaultMessage: '查看', - }) /*查看*/ - } - - - ); - }, - }, - ]; -}; - -const getLogicalDatabaseAsyncColumns = () => { - return [ - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.E38B64D9', - defaultMessage: '执行数据库', - }), - key: 'database', - dataIndex: 'database', - ellipsis: { - showTitle: true, - }, - render: (_, record) => { - const icon = getDataSourceStyleByConnectType(record?.database?.dataSource?.type); - return ( - - - - - - -
{record?.database?.name}
-
- {record?.database?.dataSource?.name} -
-
- - } - > - - - - - - -
{record?.database?.name}
-
- {record?.database?.dataSource?.name} -
-
-
-
- ); - }, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.AB5C26BA', - defaultMessage: '数据源', - }), - key: 'datasource', - dataIndex: 'datasource', - }, - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.7902B91E', - defaultMessage: '执行状态', - }), - key: 'status', - dataIndex: 'status', - }, - { - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.EDEF0329', - defaultMessage: '操作', - }), - key: 'operation', - render: (value, record) => { - return ( - - ); - }, - }, - ]; -}; -interface IProps { - taskStore?: TaskStore; - task: TaskDetail; - theme?: string; -} -const TaskProgress: React.FC = (props) => { - const { handleDetailVisible: _handleDetailVisible, setState } = useContext(TaskDetailContext); - const { task, theme, taskStore } = props; - const [subTasks, setSubTasks] = useState([]); - const [databases, setDatabases] = useState([]); - const [detailId, setDetailId] = useState(null); - const [open, setOpen] = useState(false); - const { run: loadData } = useRequest( - async () => { - const res = await getSubTask(task.id); - if (task?.type === TaskType.MULTIPLE_ASYNC) { - const sortDb = flatArray( - (task as TaskDetail)?.parameters?.orderedDatabaseIds, - ); - // @ts-ignore - const dbMap = res?.contents?.[0]?.databaseChangingRecordList?.reduce((pre, cur) => { - pre[cur?.database?.id] = cur; - return pre; - }, {}); - const rawData = []; - let rawCount = 0; - (task as TaskDetail)?.parameters?.orderedDatabaseIds?.map( - (item, index) => { - item?.forEach((_item_, _index_) => { - rawData.push({ - id: rawCount, - nodeIndex: index, - rowSpan: item?.length, - needMerge: _index_ === 0, - ...dbMap[_item_], - }); - rawCount++; - }); - }, - ); - // @ts-ignore - setSubTasks(rawData); - const databases = flatArray( - (task as TaskDetail)?.parameters?.orderedDatabaseIds, - )?.map((item) => dbMap?.[item]); - databases?.length && setDatabases(databases); - } else { - setSubTasks(res?.contents?.[0].tasks); - } - }, - { - pollingInterval: 3000, - }, - ); - const subTask = subTasks?.find((item) => item.id === detailId); - const resultJson = JSON.parse(subTask?.resultJson ?? '{}'); - const handleSwapTable = async (id: number) => { - const res = await swapTableName(id); - if (res) { - message.success( - formatMessage({ - id: 'odc.src.component.Task.component.CommonDetailModal.StartTheNameSwitching', - defaultMessage: '开始表名切换', - }), //'开始表名切换' - ); - loadData(); - } - }; - useEffect(() => { - loadData(); - }, []); - - // 终止 & 查看 - - const handleDetailVisible = (id: number) => { - setOpen(true); - setDetailId(id); - }; - const onOpenDetail = async (taskId: number) => { - const data = await getTaskDetail(taskId, true); - setState({ - detailVisible: false, - }); - taskStore.changeTaskPageType(TaskPageType.ASYNC); - _handleDetailVisible(data, true); - }; - const handleClose = () => { - setOpen(false); - }; - const getColumnsByTaskType = (type: TaskType) => { - switch (type) { - case TaskType.MULTIPLE_ASYNC: { - // return getLogicalDatabaseAsyncColumns(); - return getMultipleAsyncColumns({ - onOpenDetail, - }); - } - case TaskType.ONLINE_SCHEMA_CHANGE: { - return getColumns({ - onOpenDetail: handleDetailVisible, - onSwapTable: handleSwapTable, - }); - } - case TaskType.LOGICAL_DATABASE_CHANGE: { - return getLogicalDatabaseAsyncColumns(); - } - } - }; - const columns = getColumnsByTaskType(task?.type); - // task?.type === TaskType.MULTIPLE_ASYNC - // ? getMultipleAsyncColumns({ - // onOpenDetail, - // }) - // : getColumns({ - // onOpenDetail: handleDetailVisible, - // onSwapTable: handleSwapTable, - // }); - const pendingExectionDatabases = databases?.filter((item) => !item?.status)?.length; - return ( - <> - {task?.type === TaskType.MULTIPLE_ASYNC && subTasks?.length > 0 && ( -
- {formatMessage( - { - id: 'src.component.Task.component.CommonDetailModal.E75BF608', - defaultMessage: '共 {subTasksLength} 个数据库, {pendingExectionDatabases} 个待执行', - }, - { - subTasksLength: subTasks?.length, - pendingExectionDatabases, - }, - )} -
- )} - - - - - - - - - } - direction="column" - /> - - - - - } - direction="column" - /> - - - - ); -}; -export default inject('taskStore')(observer(TaskProgress)); diff --git a/src/component/Task/component/CommonDetailModal/TaskTools.tsx b/src/component/Task/component/CommonDetailModal/TaskTools.tsx deleted file mode 100644 index db6b35230..000000000 --- a/src/component/Task/component/CommonDetailModal/TaskTools.tsx +++ /dev/null @@ -1,339 +0,0 @@ -/* - * 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 { - rollbackDataArchiveSubTask, - startDataArchiveSubTask, - stopDataArchiveSubTask, -} from '@/common/network/task'; -import Action from '@/component/Action'; -import { - ICycleSubTaskDetailRecord, - ITaskResult, - SubTaskStatus, - SubTaskType, - TaskDetail, - TaskRecord, - TaskRecordParameters, -} from '@/d.ts'; -import type { ModalStore } from '@/store/modal'; -import type { SettingStore } from '@/store/setting'; -import type { TaskStore } from '@/store/task'; -import { formatMessage } from '@/util/intl'; -import { ExclamationCircleOutlined } from '@ant-design/icons'; -import { message, Modal, Popconfirm, Tooltip } from 'antd'; -import { inject, observer } from 'mobx-react'; -import React, { useEffect, useState } from 'react'; -interface IProps { - taskStore?: TaskStore; - settingStore?: SettingStore; - modalStore?: ModalStore; - isDetailModal?: boolean; - showRollback?: boolean; - taskId: number; - record: - | TaskRecord - | TaskDetail - | ICycleSubTaskDetailRecord; - disabledSubmit?: boolean; - result?: ITaskResult; - showLog?: boolean; - onReloadList: () => void; - onDetailVisible: (record: TaskRecord, visible: boolean) => void; - onLogVisible: (recordId: number, visible: boolean, status: SubTaskStatus) => void; - onExcecuteDetailVisible: (recordId: number, visible: boolean) => void; - onClose?: () => void; -} -const ActionBar: React.FC = inject( - 'taskStore', - 'settingStore', - 'modalStore', -)( - observer((props) => { - const { - isDetailModal, - record, - taskId, - showRollback, - showLog, - onLogVisible, - onExcecuteDetailVisible, - } = props; - const [activeBtnKey, setActiveBtnKey] = useState(null); - const resetActiveBtnKey = () => { - setActiveBtnKey(null); - }; - const _stopTask = async () => { - setActiveBtnKey('stop'); - const res = await stopDataArchiveSubTask(taskId, record.id); - if (res) { - message.success( - formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.CanceledSuccessfully', - defaultMessage: '取消成功', - }), //取消成功 - ); - - props.onReloadList(); - } - }; - const confirmRollback = async () => { - setActiveBtnKey('rollback'); - const res = await rollbackDataArchiveSubTask(taskId, record.id); - if (res) { - props.onReloadList(); - message.success( - formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.RollbackSucceeded', - defaultMessage: '回滚成功', - }), //回滚成功 - ); - } - }; - - useEffect(() => { - if (activeBtnKey) { - resetActiveBtnKey(); - } - }, [record?.status]); - const handleRollback = async () => { - Modal.confirm({ - title: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.AreYouSureYouWant', - defaultMessage: '是否确定回滚任务?', - }), - //确定回滚任务吗? - icon: , - content: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.TasksThatHaveBeenExecuted', - defaultMessage: '任务回滚后已执行的任务将重置', - }), - //任务回滚后已执行的任务将重置 - okText: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.Confirm', - defaultMessage: '确认', - }), - //确认 - cancelText: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.Cancel', - defaultMessage: '取消', - }), - //取消 - onOk: confirmRollback, - }); - }; - const handleExecute = async () => { - Modal.confirm({ - title: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.CA1CB37E', - defaultMessage: '是否确定执行任务?', - }), - icon: , - okText: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.Confirm', - defaultMessage: '确认', - }), - cancelText: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.Cancel', - defaultMessage: '取消', - }), - onOk: confirmExecute, - }); - }; - - const confirmExecute = async () => { - const res = await startDataArchiveSubTask(taskId, record.id); - if (res) { - message.success( - formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.8C3AF870', - defaultMessage: '发起执行成功', - }), - ); - props.onReloadList(); - } - }; - - const handleReTry = async () => { - const res = await startDataArchiveSubTask(taskId, record.id); - if (res) { - message.success( - formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.RetrySucceeded', - defaultMessage: '重试成功', - }), //重试成功 - ); - - props.onReloadList(); - } - }; - const handleLogVisible = async () => { - onLogVisible(record.id, true, record?.status as SubTaskStatus); - }; - const handleExcuteDetailVisible = () => { - onExcecuteDetailVisible(record.id, true); - }; - - const getTaskTools = (_task) => { - let tools = []; - if (!_task) { - return []; - } - const { status, jobGroup: type } = _task; - const rollbackBtn = { - key: 'rollback', - text: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.Rollback', - defaultMessage: '回滚', - }), - //回滚 - action: handleRollback, - type: 'button', - }; - const stopBtn = { - key: 'stop', - text: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.Termination', - defaultMessage: '终止', - }), - //终止 - action: _stopTask, - type: 'button', - }; - const executeBtn = { - key: 'execute', - text: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.Execute', - defaultMessage: '执行', - }), - //执行 - type: 'button', - action: handleExecute, - isOpenBtn: true, - isPrimary: isDetailModal, - disabled: false, - tooltip: '', - }; - const reTryBtn = { - key: 'reTry', - text: formatMessage({ - id: 'odc.component.CommonDetailModal.TaskTools.Retry', - defaultMessage: '重试', - }), - //重试 - type: 'button', - action: handleReTry, - }; - const logBtn = { - key: 'log', - text: formatMessage({ - id: 'odc.src.component.Task.component.CommonDetailModal.ViewLog', - defaultMessage: '查看日志', - }), //'查看日志' - action: handleLogVisible, - type: 'button', - }; - const excuteDetailBtn = { - key: 'excuteDetail', - text: formatMessage({ - id: 'src.component.Task.component.CommonDetailModal.11BB8886', - defaultMessage: '执行详情', - }), - action: handleExcuteDetailVisible, - type: 'button', - }; - switch (status) { - case SubTaskStatus.PREPARING: { - tools = []; - break; - } - case SubTaskStatus.RUNNING: { - tools = [stopBtn, logBtn]; - break; - } - case SubTaskStatus.CANCELED: { - tools = [executeBtn, logBtn]; - break; - } - case SubTaskStatus.DONE: { - tools = [rollbackBtn, logBtn]; - break; - } - case SubTaskStatus.FAILED: { - tools = [reTryBtn, rollbackBtn, logBtn]; - break; - } - default: - } - if ( - [ - SubTaskType.DATA_ARCHIVE, - SubTaskType.DATA_DELETE, - SubTaskType.DATA_ARCHIVE_ROLLBACK, - SubTaskType.DATA_ARCHIVE_DELETE, - ].includes(type) - ) { - tools.unshift(excuteDetailBtn); - } - return tools; - }; - const btnTools = getTaskTools(record) - ?.filter((item) => item?.type === 'button') - ?.filter((item) => (!showRollback ? item.key !== 'rollback' : true)) - ?.filter((item) => (!showLog ? item.key !== 'log' : true)); - const renderTool = (tool) => { - const ActionButton = isDetailModal ? Action.Button : Action.Link; - const disabled = activeBtnKey === tool?.key || tool?.disabled; - if (tool.confirmText) { - return ( - - {tool.text} - - ); - } - if (tool.download) { - return ( - - {tool.text} - - ); - } - return ( - - - {tool.text} - - - ); - }; - if (!btnTools?.length) { - return -; - } - return ( - - {btnTools?.map((tool) => { - return renderTool(tool); - })} - - ); - }), -); -export default ActionBar; diff --git a/src/component/Task/component/CreateTaskConfirmModal/helper.tsx b/src/component/Task/component/CreateTaskConfirmModal/helper.tsx new file mode 100644 index 000000000..7466c5def --- /dev/null +++ b/src/component/Task/component/CreateTaskConfirmModal/helper.tsx @@ -0,0 +1,5 @@ +import { IDatabase } from '@/d.ts/database'; + +export const getDefaultName = (database: IDatabase) => { + return `[${database?.environment?.name}]${database?.name}_${+new Date()}`; +}; diff --git a/src/component/Task/component/CreateTaskConfirmModal/index.less b/src/component/Task/component/CreateTaskConfirmModal/index.less new file mode 100644 index 000000000..e69de29bb diff --git a/src/component/Task/component/CreateTaskConfirmModal/index.tsx b/src/component/Task/component/CreateTaskConfirmModal/index.tsx new file mode 100644 index 000000000..7c782b843 --- /dev/null +++ b/src/component/Task/component/CreateTaskConfirmModal/index.tsx @@ -0,0 +1,74 @@ +import React, { useEffect, useState } from 'react'; +import { Button, Modal, Form, Input } from 'antd'; +import { inject, observer } from 'mobx-react'; +import { IDatabase } from '@/d.ts/database'; +import { getDefaultName } from './helper'; + +interface IProps { + onOk?: (Name: string) => void; + open: boolean; + setOpen: (data: boolean) => void; + database: IDatabase; + initName?: string; + isSchedule?: boolean; +} + +const Message = { + task: { + title: '提交工单', + label: '工单名称', + rulesMessage: '请输入工单名称', + }, + schedule: { + title: '提交作业', + label: '作业名称', + rulesMessage: '请输入作业名称', + }, +}; +const CreateTaskConfirmModal: React.FC = ({ + onOk, + open, + setOpen, + database, + initName, + isSchedule = false, +}) => { + const [form] = Form.useForm(); + const info = isSchedule ? Message.schedule : Message.task; + + useEffect(() => { + if (initName) { + form.setFieldValue('Name', initName); + } else if (open && database) { + form.setFieldValue('Name', getDefaultName(database)); + } + }, [open, database]); + + return ( + { + const value = await form.validateFields(); + onOk?.(value.Name); + setOpen(false); + }} + open={open} + onCancel={async () => { + setOpen(false); + }} + destroyOnClose + > +
+ + + + +
+ ); +}; + +export default inject('modalStore')(observer(CreateTaskConfirmModal)); diff --git a/src/component/Task/component/DataTransferModal/index.tsx b/src/component/Task/component/DataTransferModal/index.tsx index 7cce217b5..419c827bd 100644 --- a/src/component/Task/component/DataTransferModal/index.tsx +++ b/src/component/Task/component/DataTransferModal/index.tsx @@ -20,13 +20,15 @@ import { FILE_DATA_TYPE, IMPORT_TYPE, TaskExecStrategy } from '@/d.ts'; import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; import { CRLFToSeparatorString, getLocalFormatDateTime } from '@/util/utils'; -import { Alert, Col, Divider, Row, Space, Tooltip } from 'antd'; +import { Alert, Col, Divider, Row, Space, Tooltip, Tag } from 'antd'; import React from 'react'; import CsvTable from './csvTables'; import styles from './index.less'; import ObjTable from './ObjTables'; import { getImportTypeLabel } from '@/component/Task/modals/ImportTask/CreateModal/ImportForm/helper'; import { getTaskExecStrategyMap } from '@/component/Task/const'; +import { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; +import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; import EllipsisText from '@/component/EllipsisText'; const SimpleTextItem: React.FC<{ @@ -533,40 +535,11 @@ class TaskContent extends React.Component {
- - - - } - /> + } - /> - - - { } /> + + } />} + /> + + + } + /> + + + + {hasFlow && ( { id: 'odc.component.DataTransferModal.RiskLevel', defaultMessage: '风险等级', })} - /*风险等级*/ content={ - - } + content={} /> )} - + {task?.executionStrategy === TaskExecStrategy.TIMER && ( @@ -626,12 +612,6 @@ class TaskContent extends React.Component { {this.renderExt(isImport)} <> - -
{isImport @@ -656,16 +636,21 @@ class TaskContent extends React.Component { {haveCsvMapping && } -
+ + = (props) => { const dbIcon = getDataSourceStyleByConnectType(database?.dataSource?.type)?.dbIcon; return ( -
+
{!!database?.environment?.name && ( = (props) => { component={dbIcon?.component} style={{ fontSize: 16, marginRight: 4, verticalAlign: 'textBottom' }} /> -
+
= (props) => { {database?.name || '-'}
+ + {database?.remark} +
); diff --git a/src/component/Task/component/DatabaseSelect/index.tsx b/src/component/Task/component/DatabaseSelect/index.tsx index 433d47bb8..63dd51bd0 100644 --- a/src/component/Task/component/DatabaseSelect/index.tsx +++ b/src/component/Task/component/DatabaseSelect/index.tsx @@ -21,9 +21,11 @@ import { formatMessage } from '@/util/intl'; import { IDatabase } from '@/d.ts/database'; import { Form } from 'antd'; import React from 'react'; +import { ScheduleType } from '@/d.ts/schedule'; interface IProps { type?: TaskType; + scheduleType?: ScheduleType; label?: string; disabled?: boolean; name?: string | string[]; @@ -35,6 +37,7 @@ interface IProps { placeholder?: string; isLogicalDatabase?: boolean; onChange?: (v: number, database?: IDatabase) => void; + onInit?: (database?: IDatabase) => void; } const DatabaseSelect: React.FC = (props) => { const { @@ -45,6 +48,7 @@ const DatabaseSelect: React.FC = (props) => { }), //数据库 name = 'databaseId', + scheduleType, projectId, dataSourceId, filters = null, @@ -53,6 +57,7 @@ const DatabaseSelect: React.FC = (props) => { disabled = false, isLogicalDatabase = false, onChange, + onInit, } = props; return ( @@ -71,11 +76,13 @@ const DatabaseSelect: React.FC = (props) => { ]} > = ({ data, loading, taskType }) => { @@ -284,8 +285,10 @@ const ImportPreviewTable: React.FC = ({ data, loading, }, }, // 数据清理/归档有源端目标端, 分区计划和sql计划只有数据库 - ...([TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE]?.includes(taskType) ? dlmColumns : []), - ...([TaskType.SQL_PLAN, TaskType.PARTITION_PLAN]?.includes(taskType) + ...([ScheduleType.DATA_ARCHIVE, ScheduleType.DATA_DELETE]?.includes(taskType as ScheduleType) + ? dlmColumns + : []), + ...([ScheduleType.SQL_PLAN, ScheduleType.PARTITION_PLAN]?.includes(taskType as ScheduleType) ? otherScheduleColumns : []), ]?.filter(Boolean); diff --git a/src/component/Task/component/ImportModal/index.tsx b/src/component/Task/component/ImportModal/index.tsx index d7096dc19..b0d456565 100644 --- a/src/component/Task/component/ImportModal/index.tsx +++ b/src/component/Task/component/ImportModal/index.tsx @@ -1,6 +1,5 @@ import { formatMessage } from '@/util/intl'; import { getSchedulePreviewResult, startSchedulePreviewTask } from '@/common/network/task'; -// import user from '@/store/user'; import { IImportDatabaseView, IImportScheduleTaskView, diff --git a/src/component/Task/component/PartitionPolicyFormTable/configModal.tsx b/src/component/Task/component/PartitionPolicyFormTable/configModal.tsx index 0843533df..86d62345d 100644 --- a/src/component/Task/component/PartitionPolicyFormTable/configModal.tsx +++ b/src/component/Task/component/PartitionPolicyFormTable/configModal.tsx @@ -35,8 +35,8 @@ import { Tooltip, Typography, } from 'antd'; -import React, { useEffect, useMemo, useState } from 'react'; -import { ITableConfig } from '@/component/Task/modals/PartitionTask/CreateModal'; +import React, { useEffect, useState } from 'react'; +import { ITableConfig } from '@/component/Schedule/modals/PartitionPlan/Create'; import { getPartitionKeyInvokerByIncrementFieldType, INCREAMENT_FIELD_TYPE, @@ -350,7 +350,7 @@ const ConfigDrawer: React.FC = (props) => { } }, [configs]); - const submitBtn = useMemo(() => { + const submitBtn = () => { const isSingleGenerateCount = generateCount === 1; const isSingleGenerateCountMessage = formatMessage({ id: 'src.component.Task.component.PartitionPolicyFormTable.B988E243', @@ -365,7 +365,7 @@ const ConfigDrawer: React.FC = (props) => { const renderConfirmButton = () => { return ( - - {submitBtn} + {submitBtn()} } > diff --git a/src/component/Task/component/PartitionPolicyFormTable/index.tsx b/src/component/Task/component/PartitionPolicyFormTable/index.tsx index 6a3c348d5..64c9458f0 100644 --- a/src/component/Task/component/PartitionPolicyFormTable/index.tsx +++ b/src/component/Task/component/PartitionPolicyFormTable/index.tsx @@ -28,7 +28,7 @@ import { } from '@ant-design/icons'; import { Checkbox, Space, Tooltip } from 'antd'; import React, { useRef, useState } from 'react'; -import { ITableConfig } from '@/component/Task/modals/PartitionTask/CreateModal'; +import { ITableConfig } from '@/component/Schedule/modals/PartitionPlan/Create'; import { getStrategyLabel } from '../PartitionPolicyTable'; import ConfigDrawer from './configModal'; import { NameRuleType, revertPartitionKeyInvokerByIncrementFieldType, START_DATE } from './const'; @@ -209,11 +209,7 @@ const PartitionPolicyFormTable: React.FC = (props) => { const isInit = activeConfigs?.some((item) => !item?.__isCreate); let partitionConfig = activeConfigs?.[0]; if (!!createdTableConfig && isInit) { - const isLengthEqual = - createdTableConfig?.option?.partitionKeyConfigs?.length === res?.contents?.length; - if (isLengthEqual) { - partitionConfig = createdTableConfig; - } + partitionConfig = createdTableConfig; } console.log('createdTableConfig', !!createdTableConfig, isInit); console.log('partitionConfig', partitionConfig); @@ -223,7 +219,6 @@ const PartitionPolicyFormTable: React.FC = (props) => { const initNameRuleType = isInit && dateTypes ? NameRuleType.PRE_SUFFIX : NameRuleType.CUSTOM; const nameRuleType = createdTableConfig?.generateExpr ? NameRuleType.CUSTOM : initNameRuleType; - const values = activeConfigs.map((item) => { return { generateCount: null, diff --git a/src/component/Task/component/PartitionPolicyTable/index.tsx b/src/component/Task/component/PartitionPolicyTable/index.tsx index 0a3bca2c5..edbb16905 100644 --- a/src/component/Task/component/PartitionPolicyTable/index.tsx +++ b/src/component/Task/component/PartitionPolicyTable/index.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getPartitionPlan } from '@/common/network/task'; import Action from '@/component/Action'; import CommonTable from '@/component/CommonTable'; import { CommonTableMode, ITableLoadOptions } from '@/component/CommonTable/interface'; @@ -27,7 +26,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { TaskPartitionStrategyMap } from '@/component/Task/const'; import ConfigDrawer from './ConfigDrawer'; import styles from './index.less'; - +import { IScheduleRecord, IPartitionPlan } from '@/d.ts/schedule'; export const getStrategyLabel = (strategies: TaskPartitionStrategy[], split = ', ') => { return strategies?.map((item) => TaskPartitionStrategyMap[item])?.join(split); }; @@ -101,7 +100,8 @@ const ConfigStatusRender: React.FC = (enabled) => { }; interface IProps { - taskId: number; + taskId?: number; + schedule: IScheduleRecord; } interface ITableFilter { @@ -110,26 +110,15 @@ interface ITableFilter { } const PartitionPolicyTable: React.FC = (props) => { - const { taskId } = props; + const { taskId, schedule } = props; const [activeId, setActiveId] = useState(0); const [visible, setVisible] = useState(false); const [filters, setFilters] = useState(null); - const [tableConfigs, setTableConfigs] = useState([]); + const tableConfigs = schedule?.parameters?.partitionTableConfigs; const tableRef = useRef(); const tableResource = handleFilter(tableConfigs); const activeConfig = tableConfigs?.find((item) => item.id === activeId); - const loadData = async () => { - const res = await getPartitionPlan(taskId); - setTableConfigs(res?.partitionTableConfigs); - }; - - useEffect(() => { - if (taskId) { - loadData(); - } - }, [taskId]); - const columns = [ { title: formatMessage({ @@ -138,7 +127,7 @@ const PartitionPolicyTable: React.FC = (props) => { }), //'分区表' key: 'tableName', dataIndex: 'tableName', - width: 114, + width: 164, ellipsis: true, filterDropdown: (props) => { return ( @@ -164,7 +153,7 @@ const PartitionPolicyTable: React.FC = (props) => { defaultMessage: '类型', }), //'类型' ellipsis: true, - width: 80, + width: 100, render: () => Range, }, { @@ -251,16 +240,13 @@ const PartitionPolicyTable: React.FC = (props) => { mode={CommonTableMode.SMALL} ref={tableRef} titleContent={{ - title: formatMessage({ - id: 'odc.components.PartitionPolicyTable.PartitionPolicy', - defaultMessage: '分区策略', - }), //分区策略 + title: '分区策略:', }} filterContent={{ enabledSearch: false, }} onChange={handleChange} - onLoad={loadData} + onLoad={async () => {}} tableProps={{ className: styles.partitionTable, rowClassName: styles.tableRrow, diff --git a/src/component/Task/component/RollbackModal/index.tsx b/src/component/Task/component/RollbackConfirmModal/index.tsx similarity index 98% rename from src/component/Task/component/RollbackModal/index.tsx rename to src/component/Task/component/RollbackConfirmModal/index.tsx index 6df7becc0..3f8d864fd 100644 --- a/src/component/Task/component/RollbackModal/index.tsx +++ b/src/component/Task/component/RollbackConfirmModal/index.tsx @@ -23,7 +23,6 @@ const { Text } = Typography; interface IProps { open: boolean; - generateRollbackPlan: boolean; onOk: (type: RollbackType) => void; onCancel: () => void; } diff --git a/src/component/Task/component/SQLPreviewModal/index.tsx b/src/component/Task/component/SQLPreviewModal/index.tsx index 7a1f9f1bb..41e53a1a6 100644 --- a/src/component/Task/component/SQLPreviewModal/index.tsx +++ b/src/component/Task/component/SQLPreviewModal/index.tsx @@ -16,22 +16,37 @@ import { formatMessage } from '@/util/intl'; */ import { SQLCodePreviewer } from '@/component/SQLCodePreviewer'; -import { Modal } from 'antd'; +import { Modal, Form, Input } from 'antd'; import { useEffect, useState } from 'react'; +import { IDatabase } from '@/d.ts/database'; +import { getDefaultName } from '../CreateTaskConfirmModal/helper'; function SQLPreviewModal(props: { sql?: string; visible?: boolean; onClose: () => void; - onOk: () => void; + onOk: (Name?: string) => void; + database?: IDatabase; + isEdit: boolean; + initName?: string; }) { - const { sql, visible, onClose, onOk } = props; + const { sql, visible, onClose, onOk, database, isEdit = false, initName } = props; const [confirmLoading, setConfirmLoading] = useState(false); + const [form] = Form.useForm(); useEffect(() => { setConfirmLoading(false); + if (initName) { + form.setFieldValue('Name', initName); + } else if (visible && database) { + form.setFieldValue('Name', getDefaultName(database)); + } }, [visible]); + const handleOk = async (name: string) => { + onOk(name); + }; + return ( { + onOk={async () => { setConfirmLoading(true); - setTimeout(() => { - onOk(); - }); + await form + .validateFields() + .then((value) => { + handleOk(value?.Name); + }) + .catch(() => { + setConfirmLoading(false); + return false; + }); }} confirmLoading={confirmLoading} >
- +
+ +
+
+ + + +
); diff --git a/src/component/Task/component/Status/index.tsx b/src/component/Task/component/Status/index.tsx index eec606bea..35d70ed7b 100644 --- a/src/component/Task/component/Status/index.tsx +++ b/src/component/Task/component/Status/index.tsx @@ -29,13 +29,13 @@ import { CloseCircleFilled, EllipsisOutlined, ExclamationCircleFilled, + ExclamationCircleOutlined, LoadingOutlined, StopFilled, } from '@ant-design/icons'; import { Space, Tooltip } from 'antd'; import { isNil } from 'lodash'; import React from 'react'; -import { isCycleTask } from '@/component/Task/helper'; export const nodeStatus = { [TaskFlowNodeType.APPROVAL_TASK]: { [TaskNodeStatus.PENDING]: { @@ -173,6 +173,29 @@ export const status = { }), //审批中 }, + [TaskStatus.EXECUTION_SUCCEEDED_WITH_ERRORS]: { + icon: ( + + ), + text: ( + <> + 执行成功 + + + + + ), + }, + [TaskStatus.WAIT_FOR_CONFIRM]: { icon: ( = (props) => { string, { icon: React.ReactNode; - text: string; + text: string | React.ReactNode; } > = statusMap[StatusNodeType.FLOW_TASK]; if (isSubTask) { statusInfo = statusMap[StatusNodeType.SUB_TASK]; - } else if (isCycleTask(type)) { - statusInfo = statusMap[StatusNodeType.CYCLE_TASK]; } const statusObj = statusInfo[_status]; return ( diff --git a/src/component/Task/component/TaskActions/index.less b/src/component/Task/component/TaskActions/index.less new file mode 100644 index 000000000..22f735f6e --- /dev/null +++ b/src/component/Task/component/TaskActions/index.less @@ -0,0 +1,11 @@ +.taskActionsDropdown { + :global { + .ant-dropdown-menu { + .ant-dropdown-menu-item { + .ant-dropdown-menu-item-icon { + font-size: 14px; + } + } + } + } +} diff --git a/src/component/Task/component/TaskActions/index.tsx b/src/component/Task/component/TaskActions/index.tsx new file mode 100644 index 000000000..2a1ec81cc --- /dev/null +++ b/src/component/Task/component/TaskActions/index.tsx @@ -0,0 +1,863 @@ +import copy from 'copy-to-clipboard'; +import login from '@/store/login'; +import { formatMessage } from '@/util/intl'; +import { inject, observer } from 'mobx-react'; +import { ModalStore } from '@/store/modal'; +import { TaskStore } from '@/store/task'; +import type { UserStore } from '@/store/login'; +import { SettingStore } from '@/store/setting'; +import { TaskRecord, TaskRecordParameters, TaskDetail, RollbackType } from '@/d.ts'; +import useOperationPermissions from '@/util/hooks/useOperationPermissions'; +import { TaskActionsEnum } from '@/d.ts/task'; +import { + againTask, + createTask, + downloadTaskFlow, + executeTask, + getStructureComparisonTaskFile, + getTaskResult, + stopTask, + getTaskDetail, + getAsyncResultSet, +} from '@/common/network/task'; +import React, { useEffect, useMemo, useState } from 'react'; +import { Dropdown, MenuProps, message, Modal, Popconfirm, Tooltip } from 'antd'; +import { + IApplyDatabasePermissionTaskParams, + IApplyTablePermissionTaskParams, + IAsyncTaskParams, + IMultipleAsyncTaskParams, + ITaskResult, + TaskExecStrategy, + TaskStatus, + TaskType, + IResultSetExportTaskParams, + ISqlExecuteResultStatus, + IMockDataParams, + IApplyPermissionTaskParams, +} from '@/d.ts'; +import { downloadFile, getLocalFormatDateTime, uniqueTools } from '@/util/utils'; +import ipcInvoke from '@/util/client/service'; +import { openSQLResultSetViewPage } from '@/store/helper/page'; +import { TaskActionsTextMap } from '@/constant/task'; +import { + EllipsisOutlined, + BarsOutlined, + ShareAltOutlined, + CloseCircleOutlined, + CopyOutlined, +} from '@ant-design/icons'; +import { TaskStatus2Actions } from '@/component/Task/const'; +import { isClient } from '@/util/env'; +import { widthPermission } from '@/util/utils'; +import Action from '@/component/Action'; +import styles from './index.less'; +import RollBackModal from '../RollbackConfirmModal'; +import { IOperationTypeRole } from '@/d.ts/schedule'; +import { ReactComponent as RollbackSvg } from '@/svgr/Roll-back.svg'; +import Icon from '@ant-design/icons'; + +interface TaskActionsProps { + modalStore?: ModalStore; + taskStore?: TaskStore; + userStore?: UserStore; + settingStore?: SettingStore; + + disabledSubmit?: boolean; + onApprovalVisible?: (status: boolean, id: number) => void; + onDetailVisible: (task: TaskRecord, visible: boolean) => void; + onClose?: () => void; + task: Partial | TaskDetail>; + onReloadList?: () => void; + onReload?: () => void; + result?: ITaskResult; + isDetailModal?: boolean; +} + +enum actionShowScene { + list = 'list', + detail = 'detail', +} + +interface taskActionsConfig { + key: TaskActionsEnum; + label: string; + action: () => void; + /** 如果有icon,则意味着这个操作在列表页时是放在下拉菜单里的 */ + icon?: React.ReactNode; + /** 展示场景 */ + showScene: actionShowScene[]; + /** 允许的操作类型, + * 如果操作配置有allowTaskType,则需要判断工单类型是否在allowTaskType中, + * 无allowTaskType则不需要判断,默认展示 */ + allowTaskType?: TaskType[]; + visible: () => boolean; + /** 如果disabledTooltip返回不为空,则代表要禁用,并且用返回的string作为tooltip */ + disabledTooltip?: () => string; +} + +const TaskActions: React.FC = (props) => { + const { + task, + modalStore, + result, + taskStore, + settingStore, + isDetailModal, + disabledSubmit = false, + } = props; + const [openRollback, setOpenRollback] = useState(false); + const [activeBtnKey, setActiveBtnKey] = useState(); + const isSqlworkspace = location?.hash?.includes('sqlworkspace'); + const disabledApproval = + task?.status === TaskStatus.WAIT_FOR_CONFIRM && !isDetailModal ? true : disabledSubmit; + + const { IRoles } = useOperationPermissions({ + currentUserResourceRoles: task?.project?.currentUserResourceRoles || [], + approvable: task?.approvable, + createrId: task?.creator?.id, + }); + + const closeTaskDetail = async () => { + props.onDetailVisible(null, false); + }; + + const _rollbackTask = async () => { + setOpenRollback(true); + }; + + const _stopTask = async () => { + setActiveBtnKey(TaskActionsEnum.STOP); + const res = await stopTask(task.id); + if (res) { + message.success( + formatMessage({ + id: 'odc.components.TaskManagePage.TerminatedSuccessfully', + defaultMessage: '终止成功', + }), + ); + props?.onReloadList?.(); + props?.onReload?.(); + } + }; + + const _executeTask = async () => { + setActiveBtnKey(TaskActionsEnum.EXECUTE); + const res = await executeTask(task.id); + if (res) { + message.success( + formatMessage({ + id: 'src.component.Task.component.ActionBar.10A4FEFD', + defaultMessage: '开始执行', + }), + ); + closeTaskDetail(); + props?.onReloadList?.(); + } + }; + + const _approvalTask = async (status: boolean) => { + props.onApprovalVisible(status, task?.id); + }; + + const _retryTask = async () => { + const { type } = task; + + switch (type) { + case TaskType.ASYNC: { + const detailRes = (await getTaskDetail(task?.id)) as TaskDetail; + props.modalStore.changeCreateAsyncTaskModal(true, { + task: detailRes, + }); + return; + } + case TaskType.DATAMOCK: { + const detailRes = (await getTaskDetail(task?.id)) as TaskDetail; + props.modalStore.changeDataMockerModal(true, { + task: detailRes, + }); + return; + } + case TaskType.APPLY_DATABASE_PERMISSION: { + modalStore.changeApplyDatabasePermissionModal(true, { + task: task as TaskDetail, + }); + return; + } + case TaskType.APPLY_PROJECT_PERMISSION: { + modalStore.changeApplyPermissionModal(true, { + task: task as TaskDetail, + }); + return; + } + case TaskType.APPLY_TABLE_PERMISSION: { + modalStore.changeApplyTablePermissionModal(true, { + task: task as TaskDetail, + }); + return; + } + case TaskType.MULTIPLE_ASYNC: { + modalStore.changeMultiDatabaseChangeModal(true, { + projectId: (task as TaskDetail)?.parameters?.projectId, + task: task as TaskDetail, + }); + return; + } + case TaskType.SHADOW: { + modalStore.changeShadowSyncVisible(true, { + taskId: task?.id, + databaseId: task.database?.id, + }); + return; + } + case TaskType.LOGICAL_DATABASE_CHANGE: { + modalStore.changeLogicialDatabaseModal(true, { + taskId: task?.id, + }); + break; + } + case TaskType.STRUCTURE_COMPARISON: { + modalStore.changeStructureComparisonModal(true, { + databaseId: task.database?.id, + taskId: task?.id, + }); + return; + } + case TaskType.EXPORT: { + modalStore.changeExportModal(true, { + databaseId: task.database?.id, + taskId: task?.id, + }); + return; + } + case TaskType.IMPORT: { + modalStore.changeImportModal(true, { + databaseId: task.database?.id, + taskId: task?.id, + }); + return; + } + case TaskType.EXPORT_RESULT_SET: { + const detailRes = (await getTaskDetail(task?.id)) as TaskDetail; + modalStore.changeCreateResultSetExportTaskModal(true, { + databaseId: task.database?.id, + taskId: task?.id, + sql: detailRes.parameters.sql, + task: detailRes, + }); + return; + } + case TaskType.ONLINE_SCHEMA_CHANGE: { + modalStore.changeCreateDDLAlterTaskModal(true, { + databaseId: task.database?.id, + taskId: task?.id, + }); + return; + } + default: { + const { database, executionStrategy, executionTime, parameters, description } = task; + + const data = { + taskType: type, + parameters, + databaseId: database?.id, + executionStrategy, + executionTime, + description, + }; + + const res = await createTask(data); + if (res) { + message.success( + formatMessage({ + id: 'odc.TaskManagePage.component.TaskTools.InitiatedAgain', + defaultMessage: '再次发起成功', + }), + + //再次发起成功 + ); + } + } + } + }; + + const _againTask = async () => { + const { id } = task; + + const res = await againTask({ id: id }); + if (res) { + message.success( + formatMessage({ + id: 'src.component.Task.component.ActionBar.15961986', + defaultMessage: '发起重试成功', + }), + ); + props?.onReloadList?.(); + props?.onReload?.(); + } + }; + + const _download = async () => { + downloadTaskFlow(task.id); + }; + + const _downloadSQL = async () => { + const structureComparisonData = modalStore?.structureComparisonDataMap?.get(task?.id) || null; + if (structureComparisonData?.storageObjectId) { + const fileUrl = await getStructureComparisonTaskFile(task?.id, [ + `${structureComparisonData?.storageObjectId}`, + ]); + fileUrl?.forEach((url) => { + url && downloadFile(url); + }); + } + }; + + const _structureComparison = async () => { + const structureComparisonData = modalStore?.structureComparisonDataMap?.get(task?.id) || null; + structureComparisonData && + modalStore?.changeCreateAsyncTaskModal(true, { + sql: structureComparisonData?.totalChangeScript, + databaseId: structureComparisonData?.database?.id, + rules: null, + }); + }; + + const _openLocalFolder = async () => { + const info = await getTaskResult(task.id); + if (info?.exportZipFilePath) { + ipcInvoke('showItemInFolder', info?.exportZipFilePath); + } + }; + + const _downloadViewResult = async () => { + downloadFile(result?.zipFileDownloadUrl); + }; + + const _viewResult = async () => { + const resultSets = await getAsyncResultSet(task.id); + if (resultSets) { + /** + * 没有成功的请求的话,这里就不去展示结果了。 + */ + + const haveSuccessQuery = !!resultSets?.find( + (result) => result.status === ISqlExecuteResultStatus.SUCCESS && result.columns?.length, + ); + + if (!haveSuccessQuery) { + message.warning( + formatMessage({ + id: 'src.component.Task.component.ActionBar.797981FE', + defaultMessage: '无可查看的结果信息', + }), + ); + return; + } + if (isSqlworkspace) { + await openSQLResultSetViewPage( + task.id, + resultSets, + (task?.parameters as IAsyncTaskParams)?.sqlContent, + ); + taskStore.changeTaskManageVisible(false); + props.onDetailVisible(null, false); + } else { + window.open( + location.origin + + location.pathname + + `#/sqlworkspace?taskId=${task.id}&resultSets=${true}&sqlContent=${JSON.stringify( + (task?.parameters as IAsyncTaskParams)?.sqlContent, + )}`, + ); + } + } + }; + + const _shareTask = async () => { + const url = + location.origin + + location.pathname + + `#/task?taskId=${task?.id}&taskType=${task?.type}&organizationId=${login.organizationId}`; + copy(url); + message.success( + formatMessage({ + id: 'odc.src.component.Task.component.CommonDetailModal.Replication', + defaultMessage: '复制成功', + }), //'复制成功' + ); + }; + + const handleCloseRollback = async () => { + setOpenRollback(false); + }; + + const confirmRollback = async (type: RollbackType) => { + setOpenRollback(false); + closeTaskDetail(); + props.modalStore.changeCreateAsyncTaskModal(true, { + type, + task: task as TaskDetail, + databaseId: task?.database?.id, + objectId: result?.rollbackPlanResult?.resultFileDownloadUrl, + parentFlowInstanceId: task?.id, + }); + }; + + const _openTaskDetail = async () => { + props.onDetailVisible(task as TaskRecord, true); + }; + + const eventMap: Record void> = { + [TaskActionsEnum.ROLLBACK]: _rollbackTask, + [TaskActionsEnum.STOP]: _stopTask, + [TaskActionsEnum.EXECUTE]: _executeTask, + [TaskActionsEnum.PASS]: () => _approvalTask(true), + [TaskActionsEnum.REJECT]: () => _approvalTask(false), + + [TaskActionsEnum.AGAIN]: _againTask, + [TaskActionsEnum.DOWNLOAD]: _download, + [TaskActionsEnum.DOWNLOAD_SQL]: _downloadSQL, + [TaskActionsEnum.STRUCTURE_COMPARISON]: _structureComparison, + [TaskActionsEnum.OPEN_LOCAL_FOLDER]: _openLocalFolder, + + [TaskActionsEnum.DOWNLOAD_VIEW_RESULT]: _downloadViewResult, + [TaskActionsEnum.VIEW_RESULT]: _viewResult, + [TaskActionsEnum.CLONE]: _retryTask, + [TaskActionsEnum.VIEW]: _openTaskDetail, + [TaskActionsEnum.SHARE]: _shareTask, + }; + + const COMMON_ACTIONS: Array = [ + { + key: TaskActionsEnum.STOP, + label: TaskActionsTextMap[TaskActionsEnum.STOP], + action: eventMap[TaskActionsEnum.STOP], + icon: , + showScene: [actionShowScene.list, actionShowScene.detail], + visible: widthPermission( + (hasPermission) => hasPermission, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + }, + { + key: TaskActionsEnum.ROLLBACK, + label: TaskActionsTextMap[TaskActionsEnum.ROLLBACK], + action: eventMap[TaskActionsEnum.ROLLBACK], + allowTaskType: [TaskType.ASYNC, TaskType.MULTIPLE_ASYNC], + showScene: [actionShowScene.list, actionShowScene.detail], + icon: , + visible: widthPermission( + (hasPermission) => { + return hasPermission && task?.rollbackable; + }, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + }, + { + key: TaskActionsEnum.EXECUTE, + label: TaskActionsTextMap[TaskActionsEnum.EXECUTE], + action: eventMap[TaskActionsEnum.EXECUTE], + showScene: [actionShowScene.list, actionShowScene.detail], + visible: widthPermission( + (hasPermission) => { + return hasPermission && task?.executionStrategy !== TaskExecStrategy.AUTO; + }, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + disabledTooltip: () => { + if (task?.executionStrategy !== TaskExecStrategy.TIMER) { + return undefined; + } + const executionTime = getLocalFormatDateTime(task?.executionTime); + return formatMessage( + { + id: 'odc.TaskManagePage.component.TaskTools.ScheduledExecutionTimeExecutiontime', + defaultMessage: '定时执行时间:{executionTime}', + }, + + { executionTime }, + ); + }, + }, + + { + key: TaskActionsEnum.AGAIN, + label: TaskActionsTextMap[TaskActionsEnum.AGAIN], + action: eventMap[TaskActionsEnum.AGAIN], + showScene: [actionShowScene.list, actionShowScene.detail], + visible: widthPermission( + (hasPermission) => hasPermission, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + }, + { + key: TaskActionsEnum.DOWNLOAD, + label: TaskActionsTextMap[TaskActionsEnum.DOWNLOAD], + action: eventMap[TaskActionsEnum.DOWNLOAD], + showScene: [actionShowScene.list, actionShowScene.detail], + allowTaskType: [TaskType.EXPORT, TaskType.DATAMOCK, TaskType.EXPORT_RESULT_SET], + visible: widthPermission( + (hasPermission) => { + let show = hasPermission; + if (task?.type === TaskType.EXPORT) { + show = show && settingStore.enableDataExport && !isClient(); + } + return show && settingStore.enableDataExport; + }, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + disabledTooltip: () => { + const isExpired = + Math.abs(Date.now() - task?.completeTime) >= 14 * 24 * 60 * 60 * 1000 || false; + return isExpired + ? formatMessage({ + id: 'src.component.Task.component.ActionBar.F20AAC3F', + defaultMessage: '文件下载链接已超时,请重新发起工单。', + }) + : undefined; + }, + }, + { + key: TaskActionsEnum.DOWNLOAD_SQL, + showScene: [actionShowScene.detail], + label: TaskActionsTextMap[TaskActionsEnum.DOWNLOAD_SQL], + action: eventMap[TaskActionsEnum.DOWNLOAD_SQL], + allowTaskType: [TaskType.STRUCTURE_COMPARISON], + visible: widthPermission( + (hasPermission) => hasPermission, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + disabledTooltip: () => { + // 结构比对工单详情 任务未得到执行结果前禁用按钮。 + const structureComparisonData = + modalStore?.structureComparisonDataMap?.get(task?.id) || null; + const disable = + (task?.type === TaskType.STRUCTURE_COMPARISON && + structureComparisonData && + !['DONE', 'FAILED'].includes(structureComparisonData?.status)) || + !structureComparisonData?.storageObjectId; + return disable + ? formatMessage({ + id: 'src.component.Task.component.ActionBar.A79907A3', + defaultMessage: '暂不可用', + }) + : undefined; + }, + }, + { + key: TaskActionsEnum.STRUCTURE_COMPARISON, + showScene: [actionShowScene.detail], + label: TaskActionsTextMap[TaskActionsEnum.STRUCTURE_COMPARISON], + action: eventMap[TaskActionsEnum.STRUCTURE_COMPARISON], + allowTaskType: [TaskType.STRUCTURE_COMPARISON], + visible: widthPermission( + (hasPermission) => hasPermission, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + disabledTooltip: () => { + // 结构比对工单详情 任务未得到执行结果前禁用按钮。 + const structureComparisonData = + modalStore?.structureComparisonDataMap?.get(task?.id) || null; + const disable = + task?.type === TaskType.STRUCTURE_COMPARISON && + structureComparisonData && + !['DONE', 'FAILED'].includes(structureComparisonData?.status); + // 结构比对结果均为一致时,无须发起数据库变更任务。 + const noAction = + 'DONE' === structureComparisonData?.status && + ((structureComparisonData?.overSizeLimit && structureComparisonData?.storageObjectId) || + (!structureComparisonData?.overSizeLimit && + !(structureComparisonData?.totalChangeScript?.length > 0))); + if (noAction) { + return formatMessage({ + id: 'src.component.Task.component.ActionBar.D98B5B62', + defaultMessage: '结构一致,无需发起结构同步', + }); + } else if (disable) { + return formatMessage({ + id: 'src.component.Task.component.ActionBar.4BF7D8BF', + defaultMessage: '暂不可用', + }); + } else { + return undefined; + } + }, + }, + { + key: TaskActionsEnum.OPEN_LOCAL_FOLDER, + showScene: [actionShowScene.detail], + label: TaskActionsTextMap[TaskActionsEnum.OPEN_LOCAL_FOLDER], + action: eventMap[TaskActionsEnum.OPEN_LOCAL_FOLDER], + allowTaskType: [TaskType.EXPORT], + visible: widthPermission( + (hasPermission) => { + return hasPermission && settingStore.enableDataExport && isClient(); + }, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + }, + + { + key: TaskActionsEnum.DOWNLOAD_VIEW_RESULT, + showScene: [actionShowScene.detail], + label: TaskActionsTextMap[TaskActionsEnum.DOWNLOAD_VIEW_RESULT], + action: eventMap[TaskActionsEnum.DOWNLOAD_VIEW_RESULT], + allowTaskType: [TaskType.ASYNC], + visible: widthPermission( + (hasPermission) => { + const allowDownloadResultSets = + settingStore.getSpaceConfigByKey('odc.task.databaseChange.allowDownloadResultSets') === + 'true'; + return ( + hasPermission && + allowDownloadResultSets && + settingStore.enableDataExport && + result?.containQuery + ); + }, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + disabledTooltip: () => { + // 文件过期判断。 + const isExpired = + Math.abs(Date.now() - task?.completeTime) >= 14 * 24 * 60 * 60 * 1000 || false; + return isExpired + ? formatMessage({ + id: 'src.component.Task.component.ActionBar.F20AAC3F', + defaultMessage: '文件下载链接已超时,请重新发起工单。', + }) + : undefined; + }, + }, + { + key: TaskActionsEnum.VIEW_RESULT, + showScene: [actionShowScene.detail], + label: TaskActionsTextMap[TaskActionsEnum.VIEW_RESULT], + action: eventMap[TaskActionsEnum.VIEW_RESULT], + allowTaskType: [TaskType.ASYNC], + visible: widthPermission( + (hasPermission) => { + const allowShowResultSets = + settingStore.getSpaceConfigByKey('odc.task.databaseChange.allowShowResultSets') === + 'true'; + return ( + hasPermission && + task?.type === TaskType.ASYNC && + result?.containQuery && + allowShowResultSets + ); + }, + [ + IOperationTypeRole.CREATOR, + IOperationTypeRole.PROJECT_DBA, + IOperationTypeRole.PROJECT_OWNER, + ], + IRoles, + ), + }, + { + key: TaskActionsEnum.VIEW, + label: TaskActionsTextMap[TaskActionsEnum.VIEW], + action: eventMap[TaskActionsEnum.VIEW], + icon: , + showScene: [actionShowScene.list], + visible: widthPermission((hasPermission) => hasPermission, [], IRoles), + }, + { + key: TaskActionsEnum.CLONE, + label: TaskActionsTextMap[TaskActionsEnum.CLONE], + action: eventMap[TaskActionsEnum.CLONE], + icon: , + showScene: [actionShowScene.list, actionShowScene.detail], + visible: widthPermission( + (hasPermission) => hasPermission, + [IOperationTypeRole.CREATOR], + IRoles, + ), + }, + { + key: TaskActionsEnum.SHARE, + label: TaskActionsTextMap[TaskActionsEnum.SHARE], + showScene: [actionShowScene.list, actionShowScene.detail], + action: eventMap[TaskActionsEnum.SHARE], + icon: , + visible: widthPermission((hasPermission) => hasPermission, [], IRoles), + }, + { + key: TaskActionsEnum.PASS, + label: TaskActionsTextMap[TaskActionsEnum.PASS], + action: eventMap[TaskActionsEnum.PASS], + showScene: [actionShowScene.list, actionShowScene.detail], + visible: widthPermission( + (hasPermission) => { + return hasPermission && task?.approvable && !login.isPrivateSpace(); + }, + [IOperationTypeRole.APPROVER], + IRoles, + ), + disabledTooltip: () => { + return disabledApproval + ? formatMessage({ + id: 'odc.TaskManagePage.component.TaskTools.SetPartitionPoliciesForAll', + defaultMessage: '请设置所有Range分区表的分区策略', + }) + : //请设置所有Range分区表的分区策略 + null; + }, + }, + { + key: TaskActionsEnum.REJECT, + label: TaskActionsTextMap[TaskActionsEnum.REJECT], + showScene: [actionShowScene.list, actionShowScene.detail], + action: eventMap[TaskActionsEnum.REJECT], + visible: widthPermission( + (hasPermission) => { + return hasPermission && task?.approvable && !login.isPrivateSpace(); + }, + [IOperationTypeRole.APPROVER], + IRoles, + ), + }, + ]; + + const actions = useMemo(() => { + const _actions = COMMON_ACTIONS.filter((item) => { + let show = false; + /** 该状态是否有这个操作 */ + show = TaskStatus2Actions[task?.status]?.includes(item.key); + if (show && item?.allowTaskType) { + show = item?.allowTaskType.includes(task?.type) && show; + } + if (show && item?.visible) { + show = item?.visible() && show; + } + return show; + }); + return _actions; + }, [task?.status, task?.approvable]); + + const menuItems: MenuProps['items'] = useMemo(() => { + let items: MenuProps['items'] = actions + ?.map((tool) => { + if (!tool?.icon || !tool?.showScene.includes(actionShowScene.list)) return; + const { key, label, icon } = tool || {}; + const disabledTooltip = tool?.disabledTooltip?.(); + const disabled = activeBtnKey === key || Boolean(disabledTooltip); + return { + key, + label, + icon, + disabled, + }; + }) + ?.filter(Boolean); + // 判断是否需要加分割线 + if ( + items?.find((item) => item.key === TaskActionsEnum.VIEW) && + items?.[0]?.key !== TaskActionsEnum.VIEW + ) { + const viewIndex = items?.findIndex((item) => item?.key === TaskActionsEnum.VIEW); + items?.splice(viewIndex, 0, { type: 'divider' }); + } + return items; + }, [actions, activeBtnKey]); + + const renderTool = (tool: taskActionsConfig) => { + const ActionButton = isDetailModal ? Action.Button : Action.Link; + const disabledTooltip = tool?.disabledTooltip?.(); + const disabled = activeBtnKey === tool?.key || Boolean(disabledTooltip); + return ( + + {tool.label} + + ); + }; + + return ( + <> + + {actions?.map((tool) => { + /** 有icon代表着在列表页会放在下拉菜单里 */ + if (tool.icon && !isDetailModal) return; + /** 屏蔽掉不展示在详情的按钮 */ + if (isDetailModal && !tool.showScene.includes(actionShowScene.detail)) return; + if (!isDetailModal && !tool.showScene.includes(actionShowScene.list)) return; + return renderTool(tool); + })} + + {!isDetailModal && !!menuItems?.length && ( + eventMap?.[a.key]?.(), + }} + > + + + )} + + + ); +}; + +export default inject( + 'taskStore', + 'userStore', + 'modalStore', + 'settingStore', +)(observer(TaskActions)); diff --git a/src/component/Task/component/CommonDetailModal/Nodes/Items/NodeCompleteTime.tsx b/src/component/Task/component/TaskDetailModal/Nodes/Items/NodeCompleteTime.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/Nodes/Items/NodeCompleteTime.tsx rename to src/component/Task/component/TaskDetailModal/Nodes/Items/NodeCompleteTime.tsx diff --git a/src/component/Task/component/CommonDetailModal/Nodes/Items/NodeStatus.tsx b/src/component/Task/component/TaskDetailModal/Nodes/Items/NodeStatus.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/Nodes/Items/NodeStatus.tsx rename to src/component/Task/component/TaskDetailModal/Nodes/Items/NodeStatus.tsx diff --git a/src/component/Task/component/CommonDetailModal/Nodes/MultipleSQLCheckNode.tsx b/src/component/Task/component/TaskDetailModal/Nodes/MultipleSQLCheckNode.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/Nodes/MultipleSQLCheckNode.tsx rename to src/component/Task/component/TaskDetailModal/Nodes/MultipleSQLCheckNode.tsx diff --git a/src/component/Task/component/CommonDetailModal/Nodes/RollbackNode.tsx b/src/component/Task/component/TaskDetailModal/Nodes/RollbackNode.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/Nodes/RollbackNode.tsx rename to src/component/Task/component/TaskDetailModal/Nodes/RollbackNode.tsx diff --git a/src/component/Task/component/CommonDetailModal/Nodes/SQLCheckNode.tsx b/src/component/Task/component/TaskDetailModal/Nodes/SQLCheckNode.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/Nodes/SQLCheckNode.tsx rename to src/component/Task/component/TaskDetailModal/Nodes/SQLCheckNode.tsx diff --git a/src/component/Task/component/CommonDetailModal/Nodes/helper.ts b/src/component/Task/component/TaskDetailModal/Nodes/helper.ts similarity index 100% rename from src/component/Task/component/CommonDetailModal/Nodes/helper.ts rename to src/component/Task/component/TaskDetailModal/Nodes/helper.ts diff --git a/src/component/Task/component/CommonDetailModal/TaskExecuteModal.tsx b/src/component/Task/component/TaskDetailModal/TaskExecuteModal.tsx similarity index 73% rename from src/component/Task/component/CommonDetailModal/TaskExecuteModal.tsx rename to src/component/Task/component/TaskDetailModal/TaskExecuteModal.tsx index 776a5bcd4..1dac9f90e 100644 --- a/src/component/Task/component/CommonDetailModal/TaskExecuteModal.tsx +++ b/src/component/Task/component/TaskDetailModal/TaskExecuteModal.tsx @@ -3,8 +3,6 @@ import { getDataSourceStyleByConnectType } from '@/common/datasource'; import { getPhysicalExecuteDetails } from '@/common/network/logicalDatabase'; import DisplayTable from '@/component/DisplayTable'; import { ISqlExecuteResult, ISqlExecuteResultStatus } from '@/d.ts'; -import { ISchemaChangeRecord } from '@/d.ts/logicalDatabase'; -import { SqlExecuteResultStatusLabel } from '@/page/Workspace/components/SQLResultSet/const'; import DBTimeline from '@/page/Workspace/components/SQLResultSet/DBTimeline'; import { getResultText, @@ -12,10 +10,14 @@ import { } from '@/page/Workspace/components/SQLResultSet/ExecuteHistory'; import { formatTimeTemplate } from '@/util/utils'; import Icon, { InfoCircleOutlined } from '@ant-design/icons'; -import { Descriptions, Modal, Space, Tooltip } from 'antd'; +import { Descriptions, Drawer, Modal, Space, Tooltip } from 'antd'; import BigNumber from 'bignumber.js'; import { useEffect, useMemo, useState } from 'react'; import styles from './index.less'; +import { sqlExecutionResultMap } from '@/d.ts'; +import { ISchemaChangeRecord } from '@/d.ts/logicalDatabase'; +import { useRequest } from 'ahooks'; +import TaskProgressHeader from '@/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressHeader'; const getColumns = () => { return [ @@ -29,15 +31,6 @@ const getColumns = () => { render: (value: ISqlExecuteResultStatus, record, index) => { return getSqlExecuteResultStatusIcon(value); }, - onFilter: (value, record) => { - return value == record?.status; - }, - filters: Object.entries(ISqlExecuteResultStatus).map(([key, value]) => { - return { - text: SqlExecuteResultStatusLabel[key], - value: key, - }; - }), }, { title: formatMessage({ @@ -156,33 +149,33 @@ const getColumns = () => { ]; }; -const TaskProgressModal = ({ physicalDatabaseId, scheduleTaskId, modalOpen, setModalOpen }) => { +interface IProps { + modalOpen: boolean; + setModalOpen: (open: boolean) => void; + data: sqlExecutionResultMap; +} +const TaskProgressModal: React.FC = ({ modalOpen, setModalOpen, data }) => { + const [result, setResult] = useState(); const columns = getColumns(); - const [details, setDetails] = useState(); - const getLogicalDbChangeTaskJobDetails = async () => { - const res = await getPhysicalExecuteDetails(scheduleTaskId, physicalDatabaseId); - setDetails(res); + const { run: loadData, loading } = useRequest(getPhysicalExecuteDetails, { + manual: true, + }); + + const initData = async () => { + const res = await loadData(data?.id, data?.physicalDatabase?.id); + console.log(res); + setResult(res); }; useEffect(() => { - if (modalOpen && physicalDatabaseId) { - getLogicalDbChangeTaskJobDetails(); + if (modalOpen) { + initData(); } }, [modalOpen]); - const mergedData = useMemo(() => { - if (details?.sqlExecuteResults) { - return details?.sqlExecuteResults?.reduce((acc, item) => { - acc.push(item); - return acc; - }, []); - } - return []; - }, [details]); - return ( - setModalOpen(false)} - destroyOnClose + loading={loading} + onClose={() => setModalOpen(false)} footer={null} > - - - {' '} + + -
{details?.database?.name}
+
{result?.database?.name}
- {details?.database?.dataSource?.name} + {result?.database?.dataSource?.name}
- - {details?.dataSource?.name} + + {result?.completedSqlCount} + {result?.database?.dataSource?.name}
+ {result?.sqlExecuteResults && ( + + )} + -
+ ); }; export default TaskProgressModal; diff --git a/src/component/Task/component/CommonDetailModal/TaskFlow.tsx b/src/component/Task/component/TaskDetailModal/TaskFlow.tsx similarity index 99% rename from src/component/Task/component/CommonDetailModal/TaskFlow.tsx rename to src/component/Task/component/TaskDetailModal/TaskFlow.tsx index 7421bd983..97e992f82 100644 --- a/src/component/Task/component/CommonDetailModal/TaskFlow.tsx +++ b/src/component/Task/component/TaskDetailModal/TaskFlow.tsx @@ -92,7 +92,7 @@ const TaskFlow: React.FC = (props) => { createdByCurrentUser: false, approveByCurrentUser: false, parentInstanceId: task?.id, - taskType: TaskType.ALTER_SCHEDULE, + taskTypes: [TaskType.ALTER_SCHEDULE], }); const flowId = flowList?.contents?.[0]?.id; if (flowId) { diff --git a/src/component/Task/component/CommonDetailModal/TaskInfo.tsx b/src/component/Task/component/TaskDetailModal/TaskInfo.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/TaskInfo.tsx rename to src/component/Task/component/TaskDetailModal/TaskInfo.tsx diff --git a/src/component/Task/component/CommonDetailModal/TaskProgress/ProgressDetailsModal.tsx b/src/component/Task/component/TaskDetailModal/TaskProgress/ProgressDetailsModal.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/TaskProgress/ProgressDetailsModal.tsx rename to src/component/Task/component/TaskDetailModal/TaskProgress/ProgressDetailsModal.tsx diff --git a/src/component/Task/component/CommonDetailModal/TaskProgress/TaskProgressDrawer.tsx b/src/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressDrawer.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/TaskProgress/TaskProgressDrawer.tsx rename to src/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressDrawer.tsx diff --git a/src/component/Task/component/CommonDetailModal/TaskProgress/TaskProgressHeader.tsx b/src/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressHeader.tsx similarity index 75% rename from src/component/Task/component/CommonDetailModal/TaskProgress/TaskProgressHeader.tsx rename to src/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressHeader.tsx index 86c43ea16..6bcbeb5ab 100644 --- a/src/component/Task/component/CommonDetailModal/TaskProgress/TaskProgressHeader.tsx +++ b/src/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressHeader.tsx @@ -8,12 +8,20 @@ const TaskProgressHeader: React.FC<{ isLogicalDb?: boolean; }> = ({ subTasks, pendingExectionDatabases, isLogicalDb }) => { if (isLogicalDb) { - const executeCount = - subTasks?.find((i) => i?.status === SchemaChangeRecordStatus.RUNNING)?.length || 0; - const successCount = - subTasks?.find((i) => i?.status === SchemaChangeRecordStatus.SUCCESS)?.length || 0; - const failedCount = - subTasks?.find((i) => i?.status === SchemaChangeRecordStatus.FAILED)?.length || 0; + let executeCount = 0; + let successCount = 0; + let failedCount = 0; + subTasks?.forEach((i) => { + if (i?.status === SchemaChangeRecordStatus.RUNNING) { + executeCount++; + } + if (i?.status === SchemaChangeRecordStatus.SUCCESS) { + successCount++; + } + if (i?.status === SchemaChangeRecordStatus.FAILED) { + failedCount++; + } + }); return (
{formatMessage( diff --git a/src/component/Task/component/CommonDetailModal/TaskProgress/colums.tsx b/src/component/Task/component/TaskDetailModal/TaskProgress/colums.tsx similarity index 66% rename from src/component/Task/component/CommonDetailModal/TaskProgress/colums.tsx rename to src/component/Task/component/TaskDetailModal/TaskProgress/colums.tsx index 4d04b201a..0c657d57f 100644 --- a/src/component/Task/component/CommonDetailModal/TaskProgress/colums.tsx +++ b/src/component/Task/component/TaskDetailModal/TaskProgress/colums.tsx @@ -1,12 +1,17 @@ import { getDataSourceStyleByConnectType } from '@/common/datasource'; import Action from '@/component/Action'; import RiskLevelLabel from '@/component/RiskLevelLabel'; -import StatusLabel from '@/component/Task/component/Status'; import { SubTaskStatus, TaskType, TaskStatus } from '@/d.ts'; import { formatMessage } from '@/util/intl'; import { getLocalFormatDateTime } from '@/util/utils'; -import Icon, { QuestionCircleOutlined } from '@ant-design/icons'; -import { Popover, Space, Tooltip } from 'antd'; +import { Popover, Space, Tooltip, Typography } from 'antd'; +import SearchFilter from '@/component/SearchFilter'; +import Icon, { QuestionCircleOutlined, SearchOutlined } from '@ant-design/icons'; +import { SchemaChangeRecordStatus } from '@/d.ts/logicalDatabase'; +import StatusLabel, { logicDBChangeTaskStatus } from '@/component/Task/component/Status'; +import { IDatabase } from '@/d.ts/database'; +const { Link } = Typography; +import { sqlExecutionResultMap } from '@/d.ts'; const getColumns = (params: { handleDetailVisible: (id: number) => void; @@ -325,6 +330,158 @@ const getMultipleAsyncColumns = (params: { handleMultipleAsyncOpen: (taskId: num ]; }; +const getLogicalDatabaseAsyncColumns = (params: { + handleLogicalDatabaseAsyncModalOpen: (data: sqlExecutionResultMap) => void; + handleLogicalDatabaseTaskStop: (data: sqlExecutionResultMap) => void; + handleLogicalDatabaseTaskSkip: (data: sqlExecutionResultMap) => void; +}) => { + return [ + { + title: formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.7E35E39B', + defaultMessage: '执行数据库', + }), + key: 'physicalDatabase', + dataIndex: 'physicalDatabase', + ellipsis: { + showTitle: true, + }, + filterDropdown: (props) => { + return ( + + ); + }, + filterIcon: (filtered) => ( + + ), + + onFilter: (value, record: sqlExecutionResultMap) => { + return record?.physicalDatabase?.name?.includes(value); + }, + render: (physicalDatabase: IDatabase, record: sqlExecutionResultMap) => { + const icon = getDataSourceStyleByConnectType(physicalDatabase?.dataSource?.type); + + return ( + + + + +
{physicalDatabase?.name}
+
+
+ ); + }, + }, + { + title: formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.B38FABC4', + defaultMessage: '数据源', + }), + key: 'datasource', + dataIndex: 'datasource', + render: (value, record: sqlExecutionResultMap) => { + return record?.physicalDatabase?.dataSource?.name ?? '-'; + }, + filterDropdown: (props) => { + return ( + + ); + }, + filterIcon: (filtered) => ( + + ), + + onFilter: (value, record: sqlExecutionResultMap) => { + return record?.physicalDatabase?.dataSource?.name?.includes(value); + }, + }, + { + title: formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.D5F9DCA0', + defaultMessage: '执行状态', + }), + key: 'status', + dataIndex: 'status', + render: (value, row: sqlExecutionResultMap) => { + return ( + + {logicDBChangeTaskStatus[value]?.icon} + {logicDBChangeTaskStatus[value]?.text} + + ); + }, + onFilter: (value, record) => { + return value === record?.status; + }, + filters: Object.entries(SchemaChangeRecordStatus).map(([key, value]) => { + return { + text: logicDBChangeTaskStatus[key]?.text, + value: key, + }; + }), + }, + { + title: formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.13DCD7AB', + defaultMessage: '操作', + }), + key: 'operation', + render: (value, record: sqlExecutionResultMap) => { + return ( + + params?.handleLogicalDatabaseAsyncModalOpen(record)}> + {formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.178F11D7', + defaultMessage: '查看', + })} + + {record?.status === SchemaChangeRecordStatus.RUNNING && ( + params?.handleLogicalDatabaseTaskStop(record)}> + {formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.7EF67970', + defaultMessage: '终止', + })} + + )} + + {[ + SchemaChangeRecordStatus.FAILED, + SchemaChangeRecordStatus.TERMINATED, + SchemaChangeRecordStatus.TERMINATE_FAILED, + ]?.includes(record?.status as SchemaChangeRecordStatus) && ( + params?.handleLogicalDatabaseTaskSkip(record)}> + {formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.88502ED7', + defaultMessage: '跳过', + })} + + )} + + ); + }, + }, + ]; +}; + export const getColumnsByTaskType = ( type: TaskType, params: { @@ -332,6 +489,9 @@ export const getColumnsByTaskType = ( handleSwapTable; handleMultipleAsyncOpen; handleProgressDetailVisible; + handleLogicalDatabaseTaskStop; + handleLogicalDatabaseTaskSkip; + handleLogicalDatabaseAsyncModalOpen; }, status: TaskStatus, haveOperationPermission: boolean, @@ -351,5 +511,12 @@ export const getColumnsByTaskType = ( haveOperationPermission, }); } + case TaskType.LOGICAL_DATABASE_CHANGE: { + return getLogicalDatabaseAsyncColumns({ + handleLogicalDatabaseTaskStop: params?.handleLogicalDatabaseTaskStop, + handleLogicalDatabaseTaskSkip: params?.handleLogicalDatabaseTaskSkip, + handleLogicalDatabaseAsyncModalOpen: params?.handleLogicalDatabaseAsyncModalOpen, + }); + } } }; diff --git a/src/component/Task/component/CommonDetailModal/TaskProgress/index.less b/src/component/Task/component/TaskDetailModal/TaskProgress/index.less similarity index 100% rename from src/component/Task/component/CommonDetailModal/TaskProgress/index.less rename to src/component/Task/component/TaskDetailModal/TaskProgress/index.less diff --git a/src/component/Task/component/CommonDetailModal/TaskProgress/index.tsx b/src/component/Task/component/TaskDetailModal/TaskProgress/index.tsx similarity index 62% rename from src/component/Task/component/CommonDetailModal/TaskProgress/index.tsx rename to src/component/Task/component/TaskDetailModal/TaskProgress/index.tsx index 111f0ab71..e11a038db 100644 --- a/src/component/Task/component/CommonDetailModal/TaskProgress/index.tsx +++ b/src/component/Task/component/TaskDetailModal/TaskProgress/index.tsx @@ -22,6 +22,7 @@ import { TaskPageType, TaskRecordParameters, TaskType, + sqlExecutionResultMap, } from '@/d.ts'; import { TaskStore } from '@/store/task'; import { UserStore } from '@/store/login'; @@ -30,15 +31,20 @@ import { formatMessage } from '@/util/intl'; import { useRequest } from 'ahooks'; import { message } from 'antd'; import { inject, observer } from 'mobx-react'; -import React, { useContext, useEffect, useState, useMemo } from 'react'; +import React, { useContext, useEffect, useState, useMemo, useRef } from 'react'; import { flatArray } from '@/util/utils'; -import { TaskDetailContext } from '@/component/Task/container/TaskDetailContext'; +import { TaskDetailContext } from '@/component/Task/context/TaskDetailContext'; import { getColumnsByTaskType } from './colums'; import styles from './index.less'; import TaskProgressDrawer from './TaskProgressDrawer'; import TaskProgressHeader from './TaskProgressHeader'; import ProgressDetailsModal from './ProgressDetailsModal'; import { ProjectRole } from '@/d.ts/project'; +import TaskExecuteModal from '../TaskExecuteModal'; +import CommonTable from '@/component/CommonTable'; +import { IDatabase } from '@/d.ts/database'; +import { CommonTableMode } from '@/component/CommonTable/interface'; +import { skipPhysicalSqlExecute, stopPhysicalSqlExecute } from '@/common/network/logicalDatabase'; interface IProps { taskStore?: TaskStore; @@ -46,23 +52,25 @@ interface IProps { task: TaskDetail; theme?: string; onReload: () => void; + databaseList: IDatabase[]; } const TaskProgress: React.FC = (props) => { // #region ------------------------- props or state ------------------------- const { handleDetailVisible: _handleDetailVisible, setState } = useContext(TaskDetailContext); - const { task, theme, taskStore, onReload, userStore } = props; + const { task, theme, taskStore, onReload, userStore, databaseList } = props; const [subTasks, setSubTasks] = useState([]); const [databases, setDatabases] = useState([]); const [detailId, setDetailId] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); + const [modalOpen, setModalOpen] = useState(false); const [progressModalOpen, setProgressModalOpen] = useState(false); + const [result, setResult] = useState(null); + const tableRef = useRef(); + const { run: loadData } = useRequest( async () => { const res = await getSubTask(task.id); if (task?.type === TaskType.MULTIPLE_ASYNC) { - const sortDb = flatArray( - (task as TaskDetail)?.parameters?.orderedDatabaseIds, - ); // @ts-ignore const dbMap = res?.contents?.[0]?.databaseChangingRecordList?.reduce((pre, cur) => { pre[cur?.database?.id] = cur; @@ -91,7 +99,18 @@ const TaskProgress: React.FC = (props) => { )?.map((item) => dbMap?.[item]); databases?.length && setDatabases(databases); } else if (task?.type === TaskType.LOGICAL_DATABASE_CHANGE) { - setSubTasks(res?.contents); + const rawData = []; + Object.values(res?.contents?.[0]?.sqlExecutionResultMap)?.forEach((item) => { + const physicalDatabase = databaseList?.find( + (i) => i?.id === item?.result?.physicalDatabaseId, + ); + rawData.push({ + ...item, + id: item?.result?.flowInstanceId, + physicalDatabase: physicalDatabase, + }); + }); + setSubTasks(rawData); } else { setSubTasks(res?.contents?.[0].tasks); } @@ -160,6 +179,48 @@ const TaskProgress: React.FC = (props) => { }, []); // #endregion + const handleLogicalDatabaseTaskStop = async (data: sqlExecutionResultMap) => { + const res = await stopPhysicalSqlExecute(data?.id, data?.result?.physicalDatabaseId); + if (res) { + message.success( + formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.1F689C8D', + defaultMessage: '正在尝试终止', + }), + ); + } else { + message.warning( + formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.30E204EE', + defaultMessage: '当前任务状态不支持终止', + }), + ); + } + }; + const handleLogicalDatabaseTaskSkip = async (data: sqlExecutionResultMap) => { + const res = await skipPhysicalSqlExecute(data?.id, data?.result?.physicalDatabaseId); + if (res) { + message.success( + formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.B0CD0DE9', + defaultMessage: '正在尝试跳过', + }), + ); + } else { + message.warning( + formatMessage({ + id: 'src.component.Task.component.CommonDetailModal.6C8E11EA', + defaultMessage: '当前任务状态不支持跳过', + }), + ); + } + }; + + const handleLogicalDatabaseAsyncModalOpen = (data: sqlExecutionResultMap) => { + setResult(data); + setModalOpen(true); + }; + const columns = getColumnsByTaskType( task?.type, { @@ -167,6 +228,9 @@ const TaskProgress: React.FC = (props) => { handleMultipleAsyncOpen, handleSwapTable, handleProgressDetailVisible, + handleLogicalDatabaseTaskStop, + handleLogicalDatabaseTaskSkip, + handleLogicalDatabaseAsyncModalOpen, }, task.status, haveOperationPermission, @@ -177,16 +241,40 @@ const TaskProgress: React.FC = (props) => { - + {task?.type !== TaskType.LOGICAL_DATABASE_CHANGE && ( + + )} + {task?.type === TaskType.LOGICAL_DATABASE_CHANGE && ( + {}} + onChange={async () => {}} + /> + )} + = (props) => { parametersJson={parametersJson} resultJson={resultJson} /> + ); }; diff --git a/src/component/Task/component/CommonDetailModal/TaskRecord.tsx b/src/component/Task/component/TaskDetailModal/TaskRecord.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/TaskRecord.tsx rename to src/component/Task/component/TaskDetailModal/TaskRecord.tsx diff --git a/src/component/Task/component/CommonDetailModal/TaskResult.tsx b/src/component/Task/component/TaskDetailModal/TaskResult.tsx similarity index 100% rename from src/component/Task/component/CommonDetailModal/TaskResult.tsx rename to src/component/Task/component/TaskDetailModal/TaskResult.tsx diff --git a/src/component/Task/component/CommonDetailModal/index.less b/src/component/Task/component/TaskDetailModal/index.less similarity index 100% rename from src/component/Task/component/CommonDetailModal/index.less rename to src/component/Task/component/TaskDetailModal/index.less diff --git a/src/component/Task/component/CommonDetailModal/index.tsx b/src/component/Task/component/TaskDetailModal/index.tsx similarity index 66% rename from src/component/Task/component/CommonDetailModal/index.tsx rename to src/component/Task/component/TaskDetailModal/index.tsx index 41fe8b575..b51a26fe8 100644 --- a/src/component/Task/component/CommonDetailModal/index.tsx +++ b/src/component/Task/component/TaskDetailModal/index.tsx @@ -23,22 +23,20 @@ import login from '@/store/login'; import { formatMessage } from '@/util/intl'; import { ShareAltOutlined } from '@ant-design/icons'; import { Drawer, message, Radio, Spin } from 'antd'; -import copy from 'copy-to-clipboard'; -import React from 'react'; -import { isCycleTask, isLogicalDbChangeTask } from '../../helper'; +import React, { useEffect, useState } from 'react'; import styles from './index.less'; -import TaskExecuteRecord from './TaskExecuteRecord'; import TaskFlow from './TaskFlow'; import TaskInfo, { ITaskInfoProps } from './TaskInfo'; -import TaskOperationRecord from './TaskOperationRecord'; import TaskProgress from './TaskProgress'; import TaskRecord from './TaskRecord'; import TaskResult from './TaskResult'; +import { listDatabases } from '@/common/network/database'; +import { useRequest } from 'ahooks'; +import { IDatabase } from '@/d.ts/database'; + const TaskContent: React.FC = (props) => { const { task, - subTasks, - opRecord, result, isSplit, isLoading, @@ -51,18 +49,9 @@ const TaskContent: React.FC = (props) => { hasFlow, onLogTypeChange, onReload, - downloadUrl, + databaseList, } = props; let content = null; - const getDownloadUrl = () => { - if (isLogicalDbChangeTask(task?.type) && logType === CommonTaskLogType.ALL) { - return downloadUrl; - } else if (!isLogicalDbChangeTask(task?.type)) { - return result?.fullLogDownloadUrl; - } else { - return undefined; - } - }; switch (detailType) { case TaskDetailType.INFO: @@ -82,7 +71,7 @@ const TaskContent: React.FC = (props) => { log={log} logType={logType} isLoading={isLoading} - downloadUrl={getDownloadUrl()} + downloadUrl={result?.fullLogDownloadUrl} onLogTypeChange={onLogTypeChange} /> ); @@ -97,17 +86,11 @@ const TaskContent: React.FC = (props) => { case TaskDetailType.RECORD: content = ; break; - case TaskDetailType.EXECUTE_RECORD: - content = ; - break; - case TaskDetailType.OPERATION_RECORD: + case TaskDetailType.PROGRESS: content = ( - + ); break; - case TaskDetailType.PROGRESS: - content = ; - break; default: break; } @@ -121,7 +104,6 @@ interface ICommonTaskDetailModalProps extends ITaskDetailModalProps { width?: number; isSplit?: boolean; theme?: string; - downloadUrl?: string; getItems?: ( task: TaskDetail, result: ITaskResult, @@ -129,8 +111,9 @@ interface ICommonTaskDetailModalProps extends ITaskDetailModalProps { theme: string, ) => ITaskInfoProps['taskItems']; taskContent?: React.ReactNode; + databaseList?: IDatabase[]; } -const CommonTaskDetailModal: React.FC = function (props) { +const TaskDetailModal: React.FC = function (props) { const { width = 750, visible, @@ -148,14 +131,10 @@ const CommonTaskDetailModal: React.FC = function (p TaskType.IMPORT, TaskType.EXPORT, TaskType.DATAMOCK, - TaskType.PARTITION_PLAN, TaskType.SHADOW, - TaskType.SQL_PLAN, TaskType.ALTER_SCHEDULE, - TaskType.DATA_ARCHIVE, TaskType.STRUCTURE_COMPARISON, TaskType.ONLINE_SCHEMA_CHANGE, - TaskType.DATA_DELETE, TaskType.EXPORT_RESULT_SET, TaskType.APPLY_PROJECT_PERMISSION, TaskType.APPLY_DATABASE_PERMISSION, @@ -169,7 +148,6 @@ const CommonTaskDetailModal: React.FC = function (p TaskType.IMPORT, TaskType.EXPORT, TaskType.DATAMOCK, - TaskType.PARTITION_PLAN, TaskType.SHADOW, TaskType.STRUCTURE_COMPARISON, TaskType.ALTER_SCHEDULE, @@ -180,19 +158,34 @@ const CommonTaskDetailModal: React.FC = function (p TaskType.APPLY_TABLE_PERMISSION, TaskType.LOGICAL_DATABASE_CHANGE, ].includes(task?.type); - function onShare() { - const url = - location.origin + - location.pathname + - `#/task?taskId=${detailId}&taskType=${task?.type}&organizationId=${login.organizationId}`; - copy(url); - message.success( - formatMessage({ - id: 'odc.src.component.Task.component.CommonDetailModal.Replication', - defaultMessage: '复制成功', - }), //'复制成功' - ); - } + const [databaseList, setDatabaseList] = useState([]); + + const { run: fetchDatabaseList } = useRequest(listDatabases, { + manual: true, + defaultParams: [ + { + projectId: task?.projectId, + page: 1, + size: 99999, + }, + ], + }); + + const handleFetchDatabaseList = async () => { + const res = await fetchDatabaseList({ + projectId: task?.projectId, + page: 1, + size: 99999, + }); + setDatabaseList(res?.contents); + }; + + useEffect(() => { + if (visible) { + handleFetchDatabaseList(); + } + }, [visible]); + return ( = function (p id: 'odc.component.CommonTaskDetailModal.TaskDetails', defaultMessage: '任务详情', })} - {login.isPrivateSpace() ? ( -
- ) : ( - - { - formatMessage({ - id: 'odc.src.component.Task.component.CommonDetailModal.Share', - defaultMessage: '分享', - }) /* - 分享 */ - } - - - - )}
} /* 任务详情 */ destroyOnClose @@ -257,47 +235,11 @@ const CommonTaskDetailModal: React.FC = function (p )} - {task?.type === TaskType.PARTITION_PLAN && ( - - { - formatMessage({ - id: 'odc.component.CommonTaskDetailModal.AssociatedRecords', - defaultMessage: '关联记录', - }) - /*关联记录*/ - } - - )} - - {isCycleTask(task?.type) && ( - <> - - { - formatMessage({ - id: 'odc.component.CommonTaskDetailModal.ExecutionRecord', - defaultMessage: '执行记录', - }) /*执行记录*/ - } - - {!isLogicalDbChangeTask(task?.type) && ( - - { - formatMessage({ - id: 'odc.component.CommonTaskDetailModal.OperationRecord', - defaultMessage: '操作记录', - }) /*操作记录*/ - } - - )} - - )} - {[TaskType.ONLINE_SCHEMA_CHANGE, TaskType.MULTIPLE_ASYNC]?.includes(task?.type) && ( + {[ + TaskType.ONLINE_SCHEMA_CHANGE, + TaskType.MULTIPLE_ASYNC, + TaskType.LOGICAL_DATABASE_CHANGE, + ]?.includes(task?.type) && ( { formatMessage({ @@ -347,9 +289,9 @@ const CommonTaskDetailModal: React.FC = function (p
- +
{taskTools}
); }; -export default CommonTaskDetailModal; +export default TaskDetailModal; diff --git a/src/component/Task/component/CommonDetailModal/status.tsx b/src/component/Task/component/TaskDetailModal/status.tsx similarity index 75% rename from src/component/Task/component/CommonDetailModal/status.tsx rename to src/component/Task/component/TaskDetailModal/status.tsx index 8375b4023..758e48381 100644 --- a/src/component/Task/component/CommonDetailModal/status.tsx +++ b/src/component/Task/component/TaskDetailModal/status.tsx @@ -13,10 +13,7 @@ const statusMap = { [ScheduleChangeStatus.SUCCESS]: { icon: , - text: formatMessage({ - id: 'odc.component.TaskDetailDrawer.status.Complete', - defaultMessage: '完成', - }), + text: '审批完成', }, [ScheduleChangeStatus.FAILED]: { @@ -42,6 +39,18 @@ const statusMap = { defaultMessage: '改变中', }), }, + [ScheduleChangeStatus.APPROVE_CANCELED]: { + icon: , + text: '审批失败', + }, + [ScheduleChangeStatus.APPROVE_EXPIRED]: { + icon: , + text: '审批过期', + }, + [ScheduleChangeStatus.APPROVE_REJECTED]: { + icon: , + text: '审批拒绝', + }, }; export default function StatusItem(props: { status: ScheduleChangeStatus }) { diff --git a/src/component/Task/component/TaskTable/TaskNameColumn.tsx b/src/component/Task/component/TaskTable/TaskNameColumn.tsx new file mode 100644 index 000000000..a29ab13b2 --- /dev/null +++ b/src/component/Task/component/TaskTable/TaskNameColumn.tsx @@ -0,0 +1,79 @@ +import styles from '@/component/Task/index.less'; +import type { TaskRecord, TaskRecordParameters } from '@/d.ts'; +import { Tooltip } from 'antd'; +import dayjs from 'dayjs'; +import { ReactComponent as UserSvg } from '@/svgr/user.svg'; +import Icon from '@ant-design/icons'; +import classNames from 'classnames'; +import { formatMessage } from '@/util/intl'; + +interface IProps { + record: TaskRecord; + onDetailVisible: (record: TaskRecord, visible: boolean) => void; +} +const TaskNameColumn = (props: IProps) => { + const { record, onDetailVisible } = props; + const roleNames = record?.creator?.roleNames?.join(' | '); + + return ( +
+
+ { + onDetailVisible(record as TaskRecord, true); + }} + > + {record?.description} + +
+
+ { + onDetailVisible(record as TaskRecord, true); + }} + > + #{record?.id} + + · + +
创建人:{record?.creator?.name}
+
账号:{record?.creator?.accountName}
+ {roleNames && ( +
+ { + formatMessage({ + id: 'odc.component.UserPopover.Role', + defaultMessage: '角色:', + }) /*角色:*/ + }{' '} + {roleNames} +
+ )} + + } + placement="bottom" + > +
+ + {record?.creator?.name} +
+
+ 创建于 {dayjs(record?.createTime).format('YYYY-MM-DD HH:mm:ss')}· +
+ + {record?.project?.name} + +
+
+
+ ); +}; + +export default TaskNameColumn; diff --git a/src/component/Task/component/TaskTable/const.ts b/src/component/Task/component/TaskTable/const.ts index 364fada8f..3768de159 100644 --- a/src/component/Task/component/TaskTable/const.ts +++ b/src/component/Task/component/TaskTable/const.ts @@ -23,13 +23,6 @@ export const TaskTypeMap = { defaultMessage: '数据库变更', }), // 数据库变更 - - [TaskType.PARTITION_PLAN]: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTable.PartitionPlan', - defaultMessage: '分区计划', - }), - //分区计划 - [TaskType.SHADOW]: formatMessage({ id: 'odc.TaskManagePage.component.TaskTable.ShadowTableSynchronization', defaultMessage: '影子表同步', @@ -46,25 +39,10 @@ export const TaskTypeMap = { defaultMessage: '导出结果集', }), //'导出结果集' - [TaskType.SQL_PLAN]: formatMessage({ - id: 'odc.component.TaskTable.SqlPlan', - defaultMessage: 'SQL 计划', - }), - //SQL 计划 - [TaskType.DATA_ARCHIVE]: formatMessage({ - id: 'odc.component.TaskTable.DataArchiving', - defaultMessage: '数据归档', - }), - //数据归档 [TaskType.ONLINE_SCHEMA_CHANGE]: formatMessage({ id: 'odc.component.TaskTable.LockFreeStructureChange', defaultMessage: '无锁结构变更', }), - //无锁结构变更 - [TaskType.DATA_DELETE]: formatMessage({ - id: 'odc.component.TaskTable.DataCleansing', - defaultMessage: '数据清理', - }), //数据清理 [TaskType.APPLY_PROJECT_PERMISSION]: formatMessage({ id: 'odc.src.component.Task.component.TaskTable.ApplicationProjectPermissions', diff --git a/src/component/Task/component/TaskTable/index.tsx b/src/component/Task/component/TaskTable/index.tsx index 6b319e7d3..844aa267b 100644 --- a/src/component/Task/component/TaskTable/index.tsx +++ b/src/component/Task/component/TaskTable/index.tsx @@ -1,738 +1,424 @@ -/* - * 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 CommonTable from '@/component/CommonTable'; -import type { - ITableFilter, - ITableInstance, - ITableLoadOptions, - ITableSorter, -} from '@/component/CommonTable/interface'; +import type { ITableInstance, ITableLoadOptions } from '@/component/CommonTable/interface'; import { CommonTableMode, IOperationOptionType } from '@/component/CommonTable/interface'; -import SearchFilter from '@/component/SearchFilter'; -import StatusLabel, { cycleStatus, status } from '@/component/Task/component/Status'; -import { TIME_OPTION_ALL_TASK, TimeOptions } from '@/component/TimeSelect'; -import UserPopover from '@/component/UserPopover'; -import type { - ICycleTaskRecord, - IDataArchiveJobParameters, - IResponseData, - ISqlPlayJobParameters, - TaskRecord, - TaskRecordParameters, -} from '@/d.ts'; +import StatusLabel, { status } from '@/component/Task/component/Status'; +import type { IResponseData, TaskRecord, TaskRecordParameters } from '@/d.ts'; import { TaskPageType, TaskType } from '@/d.ts'; import type { PageStore } from '@/store/page'; import type { TaskStore } from '@/store/task'; -import { haveOCP, isClient } from '@/util/env'; import { useLoop } from '@/util/hooks/useLoop'; import { formatMessage } from '@/util/intl'; -import { getLocalFormatDateTime } from '@/util/utils'; -import { DownOutlined, SearchOutlined } from '@ant-design/icons'; -import { Button, DatePicker, Tooltip, Popover, Space, Typography } from 'antd'; -import { flatten } from 'lodash'; +import Icon, { DownOutlined, SearchOutlined } from '@ant-design/icons'; +import { Button, DatePicker, Tooltip, Popover, Space, Typography, Dropdown } from 'antd'; import { inject, observer } from 'mobx-react'; -import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; -import React, { useEffect, useRef, useState, useContext } from 'react'; -import { getTaskGroupLabels, getTaskLabelByType, isCycleTaskPage } from '@/component/Task/helper'; +import React, { useEffect, useRef, useState, useContext, useCallback, useMemo } from 'react'; import styles from '@/component/Task/index.less'; -import TaskTools from '../ActionBar'; import { listProjects } from '@/common/network/project'; import ProjectContext from '@/page/Project/ProjectContext'; import { isProjectArchived } from '@/page/Project/helper'; -import { useRequest } from 'ahooks'; -import useUrlAction, { URL_ACTION } from '@/util/hooks/useUrlAction'; +import { useRequest, useSetState } from 'ahooks'; import useURLParams from '@/util/hooks/useUrlParams'; -import { TASK_EXECUTE_DATE_KEY, TASK_EXECUTE_TIME_KEY, TaskTypeMap } from './const'; -import { getStatusFilters } from './utils'; -import { AsyncTaskOperationButton } from '../AsyncTaskOperationButton'; -import { - getExportConfig, - getTerminateConfig, - isScheduleMigrateTask, -} from '../AsyncTaskOperationButton/helper'; -const { RangePicker } = DatePicker; -const { Text, Link } = Typography; -import ImportModal from '../ImportModal'; -import { useImport } from '../ImportModal/useImport'; -import { useTaskSelection, taskTypeThatCanBeExport } from './useTaskSelection'; -import login from '@/store/login'; +import { useTaskGroup } from '../../hooks'; +import { TaskConfig } from '@/common/task'; +import Header from '../../layout/Header'; +import TableCard from '@/component/Table/TableCard'; +import ParamsContext, { defaultParam } from '../../context/ParamsContext'; +import { debounce } from 'lodash'; +import { ITaskParam, TaskPageMode, IPagination } from '@/component/Task/interface'; +import { TaskPageTextMap } from '@/constant/task'; +import { useTaskSelection } from '@/component/Task/component/TaskTable/useTaskSelection'; +import useUrlAction, { URL_ACTION } from '@/util/hooks/useUrlAction'; +import { getTerminateConfig } from '@/component/Task/component/AsyncTaskOperationButton/helper'; +import { AsyncTaskOperationButton } from '@/component/Task/component/AsyncTaskOperationButton'; +import ImportModal from '@/component/Task/component/ImportModal'; +import { useImport } from '@/component/Task/component/ImportModal/useImport'; +import TaskNameColumn from './TaskNameColumn'; import odc from '@/plugins/odc'; +import TaskActions from '../TaskActions'; +import { taskTypeThatCanBeTerminate } from '@/constant/triangularization'; +import { TASK_EXECUTE_DATE_KEY, TASK_EXECUTE_TIME_KEY } from './const'; +const { Text } = Typography; interface IProps { tableRef: React.RefObject; taskStore?: TaskStore; pageStore?: PageStore; taskTabType?: TaskPageType; - taskList: IResponseData< - | TaskRecord - | ICycleTaskRecord - >; - - isMultiPage?: boolean; - getTaskList: (args: ITableLoadOptions, executeDate: [Dayjs, Dayjs] | []) => Promise; + taskList: IResponseData>; + mode?: TaskPageMode; + getTaskList: (args: ITableLoadOptions, pagination: IPagination) => Promise; onReloadList: () => void; onDetailVisible: (task: TaskRecord, visible: boolean) => void; onChange?: (args: ITableLoadOptions) => void; onMenuClick?: (type: TaskPageType) => void; disableProjectCol?: boolean; + loading: boolean; + setLoading: React.Dispatch>; + onApprovalVisible?: (status: boolean, id: number) => void; + params?: ITaskParam; + setParams?: React.Dispatch>; + pagination?: IPagination; + setPagination?: React.Dispatch>; } -const TaskTable: React.FC = inject( - 'taskStore', - 'pageStore', -)( - observer((props) => { - const { - taskStore, - pageStore, - taskTabType, - tableRef, - taskList, - isMultiPage, - disableProjectCol, - } = props; - const { taskPageScope } = taskStore; - const taskStatusFilters = getStatusFilters(isCycleTaskPage(taskTabType) ? cycleStatus : status); - - const { data: projects } = useRequest(listProjects, { - defaultParams: [null, 1, 40], - }); - const projectOptions = projects?.contents?.map(({ name, id }) => ({ - text: name, - value: id?.toString(), - })); - const { getParam } = useURLParams(); - const urlStatusValue = getParam('status'); - const urlTriggerValue = getParam('filtered'); +const TaskTable: React.FC = (props) => { + const { + taskStore, + pageStore, + getTaskList, + taskTabType, + tableRef, + taskList, + mode, + onMenuClick, + onDetailVisible, + loading, + setLoading, + onApprovalVisible, + params, + setParams, + pagination, + setPagination, + } = props; + const { results: menus } = useTaskGroup({ taskItems: Object.values(TaskConfig) }); + const { project } = useContext(ProjectContext) || {}; + const projectArchived = isProjectArchived(project); + const { activePageKey } = pageStore; + const isAll = TaskPageType.ALL === taskTabType; + const [hoverInNewTaskMenuBtn, setHoverInNewTaskMenuBtn] = useState(false); + const [hoverInNewTaskMenu, setHoverInNewTaskMenu] = useState(false); + const [importModalVisible, setImportModalVisible] = useState(false); + const [importProjectId, setImportProjectId] = useState(); + const { isSubmitImport, debounceSubmit } = useImport(props.onReloadList, importProjectId); + const { runAction } = useUrlAction(); - const currentTask = taskList; - const [executeTime, setExecuteTime] = useState(() => { - return JSON.parse(localStorage?.getItem(TASK_EXECUTE_TIME_KEY)) ?? 7; - }); - const [executeDate, setExecuteDate] = useState<[Dayjs, Dayjs] | []>(() => { - const [start, end] = JSON.parse(localStorage?.getItem(TASK_EXECUTE_DATE_KEY)) ?? [null, null]; - return !start || !end ? null : [dayjs(start), dayjs(end)]; - }); - const [loading, setLoading] = useState(false); - const [hoverInNewTaskMenuBtn, setHoverInNewTaskMenuBtn] = useState(false); - const [hoverInNewTaskMenu, setHoverInNewTaskMenu] = useState(false); - const [listParams, setListParams] = useState(null); - const [delTaskList, setDelTaskList] = useState([]); - const [importModalVisible, setImportModalVisible] = useState(false); - const [importProjectId, setImportProjectId] = useState(); - const { project } = useContext(ProjectContext) || {}; - const projectArchived = isProjectArchived(project); - const loadParams = useRef(null); - const { activePageKey } = pageStore; - const columns = initColumns(listParams); - const { runAction } = useUrlAction(); - const { isSubmitImport, debounceSubmit } = useImport(props.onReloadList, importProjectId); + const { loop: loadData, destory } = useLoop((count) => { + return async (args, propsPagination) => { + if (mode === TaskPageMode.MULTI_PAGE && activePageKey !== taskTabType) { + destory(); + return; + } + if (propsPagination?.pageSize) { + setPagination(propsPagination); + } + await getTaskList(args, propsPagination); + setLoading(false); + }; + }, 6000); - const { selectedRow, rowSelection, clearSelection } = useTaskSelection({ - taskStore, - taskTabType, - taskList, - tableRef, + useEffect(() => { + setLoading(true); + runAction({ actionType: URL_ACTION.newTask, callback: () => setHoverInNewTaskMenu(true) }); + runAction({ + actionType: URL_ACTION.newDataMock, + callback: () => { + props.onMenuClick(TaskPageType.DATAMOCK); + }, }); + loadData(params, pagination); + return () => { + destory?.(); + }; + }, []); - const { loop: loadData, destory } = useLoop((count) => { - return async (args: ITableLoadOptions) => { - const _executeTime = args?.filters?.executeTime ?? executeTime; - loadParams.current = args; - if (isMultiPage && activePageKey !== taskTabType) { - destory(); - return; - } - setExecuteTime(_executeTime); - const filters = { - ...args?.filters, - status: Array.from( - new Set((args?.filters?.status || []).concat(urlStatusValue ? [urlStatusValue] : [])), - ), - executeTime: urlStatusValue ? TIME_OPTION_ALL_TASK : _executeTime, - }; - - setListParams({ - ...args, - filters, - }); - - // 只有在执行时间不为"全部"时才传递 executeDate - const shouldUseExecuteDate = filters.executeTime !== TIME_OPTION_ALL_TASK; - await props.getTaskList( - { - ...args, - filters, - }, - shouldUseExecuteDate ? executeDate : [], - ); - setLoading(false); - }; - }, 6000); - - useEffect(() => { - runAction({ actionType: URL_ACTION.newTask, callback: () => setHoverInNewTaskMenu(true) }); - }, []); + const { data: resProjects } = useRequest(listProjects, { + defaultParams: [null, 1, 400], + }); - useEffect(() => { - if (executeTime) { - localStorage.setItem(TASK_EXECUTE_TIME_KEY, JSON.stringify(executeTime)); - } - }, [executeTime]); + const handleChangeParams = useCallback( + debounce((params, pagination) => { + setLoading(true); + loadData(params, pagination); + }, 150), + [pagination, params], + ); - useEffect(() => { - loadData(loadParams.current); - }, [executeDate]); + useEffect(() => { + setPagination({ + current: taskList?.page?.number, + pageSize: taskList?.page?.size ? taskList?.page?.size : pagination?.pageSize, + }); + }, [taskList]); - useEffect(() => { - if (loadParams.current) { - setLoading(true); - loadData({ - ...loadParams.current, - filters: null, - sorter: null, - pagination: { - current: 1, - }, - }); - } - }, [taskPageScope, taskTabType, activePageKey]); + useEffect(() => { + params.timeRange && + localStorage.setItem(TASK_EXECUTE_TIME_KEY, JSON.stringify(params.timeRange)); + params.executeDate && + localStorage.setItem(TASK_EXECUTE_DATE_KEY, JSON.stringify(params.executeDate)); + handleChangeParams(params, { + ...pagination, + current: 1, + }); + }, [params, taskTabType]); - useEffect(() => { - if (executeTime) { - localStorage.setItem(TASK_EXECUTE_TIME_KEY, JSON.stringify(executeTime)); - } - }, [executeTime]); + const { selectedRow, rowSelection, clearSelection } = useTaskSelection({ + taskStore, + taskTabType, + taskList, + tableRef, + }); - function initColumns(listParams: { filters: ITableFilter; sorter: ITableSorter }) { - const { filters, sorter } = listParams ?? {}; - const columns = [ - { - dataIndex: 'id', - key: 'id', - title: formatMessage({ - id: 'odc.component.TaskTable.No', - defaultMessage: '编号', - }), - //编号 - filterDropdown: (props) => { - return ( - { + return ( + setHoverInNewTaskMenuBtn(true)} + onMouseLeave={() => setHoverInNewTaskMenuBtn(false)} + > + {menus?.map((groupItem) => { + if (!groupItem.label) { + return; + } + return ( + + + {groupItem?.label} + + + {groupItem?.children?.map((item) => { + return ( +
{ + setHoverInNewTaskMenuBtn(false); + onMenuClick(item.value); + }} + > + {item?.label} +
+ ); })} +
+
+ ); + })} +
+ ); + }; - /*请输入编号*/ - /> - ); - }, - filterIcon: (filtered) => ( - - ), - - filteredValue: filters?.id || null, - filters: [], - ellipsis: true, - width: 80, - }, - { - dataIndex: 'type', - key: 'type', - title: formatMessage({ - id: 'odc.component.TaskTable.Type', - defaultMessage: '类型', - }), - //类型 - ellipsis: true, - width: 100, - render: (type, record) => { - return TaskTypeMap[type === TaskType.ALTER_SCHEDULE ? record?.parameters?.type : type]; - }, - }, - disableProjectCol - ? null - : { - dataIndex: 'project', - key: 'projectIdList', - title: formatMessage({ - id: 'src.component.Task.component.TaskTable.CDB513DC', - defaultMessage: '项目', - }), - filters: projectOptions, - filteredValue: filters?.projectIdList || null, - ellipsis: true, - width: 80, - render(project) { - return project?.name || '-'; - }, + const columns = [ + { + title: '工单', + dataIndex: 'id', + width: 500, + render: (id, record) => , + }, + { + ...(isAll + ? { + title: '类型', + dataIndex: 'type', + width: 150, + render: (type, record) => { + return TaskPageTextMap[type]; }, - { - dataIndex: 'description', - key: 'description', - title: formatMessage({ - id: 'odc.component.TaskTable.TicketDescription', - defaultMessage: '工单描述', - }), - width: 100, - //工单描述 - ellipsis: { - showTitle: false, - }, - render: (description) => {description || '-'}, - }, - { - dataIndex: 'candidateApprovers', - key: 'candidateApprovers', - title: formatMessage({ - id: 'odc.component.TaskTable.CurrentHandler', - defaultMessage: '当前处理人', - }), - //当前处理人 - ellipsis: true, - width: 115, - render: (candidateApprovers) => - candidateApprovers?.map((item) => item.name)?.join(', ') || '-', - }, - { - dataIndex: 'creator', - key: 'creator', - title: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTable.Created', - defaultMessage: '创建人', - }), - //创建人 - width: 80, - ellipsis: { - showTitle: false, - }, - filterDropdown: (props) => { - return ( - - ); - }, - onFilter: (value, record) => - record?.creator?.name?.toLowerCase()?.includes(value?.toLowerCase()), - filterIcon: (filtered) => ( - - ), + { + title: formatMessage({ + id: 'odc.component.TaskTable.Status', + defaultMessage: '状态', + }), + dataIndex: 'status', + width: 100, + render: (status, record) => { + return ; + }, + }, + { + title: '操作', + dataIndex: 'actions', + width: 200, + render: (_, record) => ( + props.onDetailVisible(null, false)} + onApprovalVisible={onApprovalVisible} + /> + ), + }, + ]; - filteredValue: filters?.creator || null, - filters: [], - render: (creator) => { - return ( - - ); - }, - }, - { - dataIndex: 'createTime', - key: 'createTime', - title: formatMessage({ - id: 'odc.components.TaskManagePage.CreationTime', - defaultMessage: '创建时间', - }), - render: (time: number) => getLocalFormatDateTime(time), - sorter: true, - sortOrder: sorter?.columnKey === 'createTime' && sorter?.order, - width: 180, - }, - { - dataIndex: 'status', - key: 'status', - title: formatMessage({ - id: 'odc.component.TaskTable.Status', - defaultMessage: '状态', - }), - //状态 - width: 120, - filters: taskStatusFilters, - defaultFilteredValue: urlStatusValue ? [urlStatusValue] : [], - onFilter: (value, record) => { - return record.status == value; - }, - filteredValue: filters?.status || null, - render: (status, record) => ( - - ), - }, - { - dataIndex: 'deal', + const newALLTaskOperation = useMemo(() => { + if (!isAll || projectArchived) return; + + return ( + + + + ); + }, [isAll, hoverInNewTaskMenuBtn, hoverInNewTaskMenu]); - key: 'deal', - title: formatMessage({ - id: 'odc.components.TaskManagePage.Operation', - defaultMessage: '操作', - }), - width: 150, - render: (_, record) => ( - - ), - }, - ].filter(Boolean); + const newTaskOperation = useMemo(() => { + if (isAll || projectArchived) return; + const taskTypeLabel = TaskPageTextMap[taskTabType]; + return ( + + ); + }, [isAll, taskTabType, isSubmitImport]); - return !isClient() ? columns : columns.filter((item) => item.dataIndex !== 'creator'); + const batchOperation = () => { + if (projectArchived) return; + let menuItems = []; + const isSupportTaksTerminate = odc?.appConfig?.task?.isSupportTaksTerminate; + if (isSupportTaksTerminate && taskTypeThatCanBeTerminate?.includes(taskTabType)) { + menuItems.push({ + key: 'batchTerminate', + label: ( + { + clearSelection(); + props.onReloadList?.(); + }} + {...getTerminateConfig(selectedRow)} + buttonType="text" + dataSource={selectedRow} + /> + ), + }); } - const handleChange = (params: ITableLoadOptions) => { - loadData(params); - }; - const handleReload = () => { - loadData(listParams); - }; - const isAll = [ - TaskPageType.ALL, - TaskPageType.APPROVE_BY_CURRENT_USER, - TaskPageType.CREATED_BY_CURRENT_USER, - ].includes(taskTabType); - const menus = getTaskGroupLabels()?.filter((item) => !!item.groupName); - const activeTaskLabel = getTaskLabelByType(taskTabType); - - const newTaskMenu = () => { - const items = flatten( - menus - ?.map(({ group, groupName }, index) => { - const tasks = group?.filter((task) => task.enabled); - if (tasks.length === 0) { - return null; - } - return { - key: index, - label: groupName, - children: tasks?.map((item) => { - return { - key: item.value, - label: item.label, - }; - }), - type: 'group', - }; - }) - .filter(Boolean), - ); - return ( - setHoverInNewTaskMenuBtn(true)} - onMouseLeave={() => setHoverInNewTaskMenuBtn(false)} - > - {items?.map((i) => { - return ( - - - {i?.label} - - - {i?.children?.map((i) => { - return ( -
{ - setHoverInNewTaskMenuBtn(false); - props.onMenuClick(i?.key as TaskPageType); - }} - > - {i?.label} -
- ); - })} -
-
- ); + if (!menuItems.length) return; + return ( + + + + ); + }; - - - - ), - }, - ]; + return ( + + {newALLTaskOperation} + {newTaskOperation} + {batchOperation()} +
} - const isSupportTaksImport = odc?.appConfig?.task?.isSupportTaksImport; - const isSupportTaksExport = odc?.appConfig?.task?.isSupportTaksExport; - const isSupportTaksTerminate = odc?.appConfig?.task?.isSupportTaksTerminate; - return [ - !taskTypeThatCanBeExport.includes(taskTabType) || - login.isPrivateSpace() || - !isSupportTaksImport - ? { - type: IOperationOptionType.button, - content: [ - TaskPageType.APPLY_PROJECT_PERMISSION, - TaskPageType.APPLY_DATABASE_PERMISSION, - TaskPageType.APPLY_TABLE_PERMISSION, - ].includes(taskTabType) - ? activeTaskLabel - : formatMessage( - { - id: 'odc.src.component.Task.component.TaskTable.NewActiveTasklabel', - defaultMessage: '新建{activeTaskLabel}', - }, - { activeTaskLabel }, - ), - //`新建${activeTaskLabel}` - isPrimary: true, - onClick: () => { - props.onMenuClick(taskTabType); - }, - } - : { - type: IOperationOptionType.dropdown, - trigger: ['hover'] as ('contextMenu' | 'hover' | 'click')[], - content: ( - - ), - - menu: { - items: [ - { - key: 'import', - label: formatMessage( - { - id: 'src.component.Task.component.TaskTable.D4FAED98', - defaultMessage: '导入{activeTaskLabel}', - }, - { activeTaskLabel }, - ), - onClick: () => { - setImportModalVisible(true); - }, - disabled: isSubmitImport, - tooltip: isSubmitImport - ? formatMessage({ - id: 'src.component.Task.component.TaskTable.55FC08BB', - defaultMessage: '正在导入中', - }) - : '', - }, - ], - }, - onClick: () => { - props.onMenuClick(taskTabType); - }, - }, - isScheduleMigrateTask(taskTabType as any) - ? { - type: IOperationOptionType.custom, - visible: isSupportTaksExport, - render: () => ( - { - clearSelection(); - props.onReloadList?.(); - }} - {...getExportConfig(selectedRow)} - dataSource={selectedRow} - /> - ), - } - : null, - [ - TaskPageType.ALL, - TaskPageType.STRUCTURE_COMPARISON, - TaskPageType.MULTIPLE_ASYNC, - ]?.includes(taskTabType) - ? null - : { - type: IOperationOptionType.custom, - visible: isSupportTaksTerminate, - render: () => ( - { - clearSelection(); - props.onReloadList?.(); - }} - {...getTerminateConfig(selectedRow)} - dataSource={selectedRow} - /> - ), + extra={ + { + setLoading(true); + loadData(params, pagination); }, - ]?.filter(Boolean); - }; - - return ( - <> - { - const content = executeTime === 'custom' && ( - { - return current > dayjs(); - }} - format="YYYY-MM-DD HH:mm:ss" - onChange={(value) => { - setExecuteDate(value); - localStorage.setItem(TASK_EXECUTE_DATE_KEY, JSON.stringify(value)); - }} - /> - ); - return content; - }, - }, - ], - }} - onLoad={loadData} - onChange={handleChange} - tableProps={{ - className: styles.commonTable, - rowClassName: styles.tableRrow, - columns: columns as any, - dataSource: currentTask?.contents, - rowKey: 'id', - loading: loading, - pagination: urlTriggerValue - ? false - : { - current: currentTask?.page?.number, - total: currentTask?.page?.totalElements, - }, - }} - rowSelecter={ - [ - TaskPageType.ALL, - TaskPageType.STRUCTURE_COMPARISON, - TaskPageType.MULTIPLE_ASYNC, - ]?.includes(taskTabType) - ? null - : rowSelection + > + +
+ + + } + > + { + loadData(params, { + current: 1, + pageSize: e?.pageSize ? e?.pageSize : pagination?.pageSize, + }); + }} + onChange={(e) => { + if (e.pagination) { + loadData(params, { + pageSize: e?.pagination?.pageSize ? e?.pagination?.pageSize : pagination?.pageSize, + current: e?.pagination?.current, + }); } - showSelectedInfoBar={false} - /> + }} + enabledReload={false} + tableProps={{ + className: styles.commonTable, + loading, + columns: columns, + rowKey: 'id', + dataSource: taskList?.contents, + pagination: { + current: taskList?.page?.number, + total: taskList?.page?.totalElements, + }, + }} + showSelectedInfoBar={false} + rowSelecter={[TaskPageType.ALL]?.includes(taskTabType) ? null : rowSelection} + /> + setImportModalVisible(false)} + onOk={(scheduleTaskImportRequest, previewData, projectId) => { + setImportModalVisible(false); + setImportProjectId(projectId); + debounceSubmit(scheduleTaskImportRequest, previewData); + }} + /> + + ); +}; - setImportModalVisible(false)} - onOk={(scheduleTaskImportRequest, previewData, projectId) => { - setImportModalVisible(false); - setImportProjectId(projectId); - debounceSubmit(scheduleTaskImportRequest, previewData); - }} - /> - - ); - }), -); -export default TaskTable; +export default inject('taskStore', 'pageStore')(observer(TaskTable)); diff --git a/src/component/Task/component/TaskTable/useTaskSelection.ts b/src/component/Task/component/TaskTable/useTaskSelection.ts index 356e2b26b..281f5baac 100644 --- a/src/component/Task/component/TaskTable/useTaskSelection.ts +++ b/src/component/Task/component/TaskTable/useTaskSelection.ts @@ -1,48 +1,18 @@ import { useEffect, useState, useMemo } from 'react'; -import type { - TaskRecord, - TaskRecordParameters, - ICycleTaskRecord, - ISqlPlayJobParameters, - IDataArchiveJobParameters, - IResponseData, -} from '@/d.ts'; +import type { TaskRecord, TaskRecordParameters, ICycleTaskRecord, IResponseData } from '@/d.ts'; import { TaskPageType, TaskStatus } from '@/d.ts'; import type { TaskStore } from '@/store/task'; import type { ITableInstance } from '@/component/CommonTable/interface'; import odc from '@/plugins/odc'; - -export const statusThatCanBeExport = Object.keys(TaskStatus); - -export const statusThatCanBeTerminate = [ - TaskStatus.CREATING, - TaskStatus.APPROVING, - TaskStatus.ENABLED, - TaskStatus.PAUSE, - TaskStatus.EXECUTING, - TaskStatus.WAIT_FOR_EXECUTION, - TaskStatus.CREATED, -]; - -export const taskTypeThatCanBeExport = [ - TaskPageType.SQL_PLAN, - TaskPageType.DATA_ARCHIVE, - TaskPageType.DATA_DELETE, - TaskPageType.PARTITION_PLAN, -]; +import { taskStatusThatCanBeTerminate } from '@/constant/triangularization'; interface UseTaskSelectionProps { taskStore: TaskStore; taskTabType: TaskPageType; - taskList: IResponseData< - | TaskRecord - | ICycleTaskRecord - >; + taskList: IResponseData>; tableRef: React.RefObject; } -const isSupportTaksExport = odc?.appConfig?.task?.isSupportTaksExport; -const isSupportTaksImport = odc?.appConfig?.task?.isSupportTaksImport; const isSupportTaksTerminate = odc?.appConfig?.task?.isSupportTaksTerminate; export const useTaskSelection = ({ @@ -61,9 +31,7 @@ export const useTaskSelection = ({ // 当任务列表数据变化时,清理无效的selectedRowKeys useEffect(() => { if (taskList?.contents?.length > 0 && taskStore.selectedRowKeys.length > 0) { - const rules = taskTypeThatCanBeExport.includes(taskTabType) - ? [...statusThatCanBeExport, ...statusThatCanBeTerminate] - : [...statusThatCanBeTerminate]; + const rules = [...taskStatusThatCanBeTerminate]; const validSelectedRowKeys = taskStore.selectedRowKeys.filter((keyId) => { const taskInCurrentList = taskList.contents.find((task) => task.id === keyId); @@ -97,9 +65,7 @@ export const useTaskSelection = ({ }, [taskStore.selectedRowKeys]); const rowSelection = useMemo(() => { - const rules = taskTypeThatCanBeExport.includes(taskTabType) - ? [...statusThatCanBeExport, ...statusThatCanBeTerminate] - : [...statusThatCanBeTerminate]; + const rules = [...taskStatusThatCanBeTerminate]; return { selectedRowKeys: taskStore.selectedRowKeys, @@ -111,11 +77,7 @@ export const useTaskSelection = ({ taskStore.setSelectedRowKeys(selectedRowKeys); setSelectedRow(selectedRows); }, - getCheckboxProps: ( - record: - | TaskRecord - | ICycleTaskRecord, - ) => { + getCheckboxProps: (record: TaskRecord | ICycleTaskRecord) => { return { disabled: !rules?.includes(record.status), name: record.id?.toString(), @@ -132,10 +94,7 @@ export const useTaskSelection = ({ return { selectedRow, setSelectedRow, - rowSelection: - isSupportTaksExport || isSupportTaksImport || isSupportTaksTerminate - ? rowSelection - : undefined, + rowSelection: isSupportTaksTerminate ? rowSelection : undefined, clearSelection, }; }; diff --git a/src/component/Task/component/VariableConfigTable/index.tsx b/src/component/Task/component/VariableConfigTable/index.tsx index bda5b9ddc..7250eba49 100644 --- a/src/component/Task/component/VariableConfigTable/index.tsx +++ b/src/component/Task/component/VariableConfigTable/index.tsx @@ -15,7 +15,7 @@ */ import DisplayTable from '@/component/DisplayTable'; -import { timeUnitOptions } from '@/component/Task/modals/DataArchiveTask/CreateModal/VariableConfig'; +import { timeUnitOptions } from '@/component/Schedule/modals/DataArchive/Create/VariableConfig'; import { formatMessage } from '@/util/intl'; import React from 'react'; const oprationReg = /^[-+]\d+[shdwmMy]$/; diff --git a/src/component/Task/const.ts b/src/component/Task/const.ts index 6c722177c..1e6ed29da 100644 --- a/src/component/Task/const.ts +++ b/src/component/Task/const.ts @@ -20,8 +20,10 @@ import { TaskErrorStrategy, TaskExecStrategy, TaskPartitionStrategy, + TaskStatus, TaskType, } from '@/d.ts'; +import { TaskActionsEnum } from '@/d.ts/task'; import { formatMessage } from '@/util/intl'; export const ErrorStrategyMap = { @@ -108,40 +110,22 @@ export const OscMaxDataSizeLimit = 1000; export const getTaskExecStrategyMap = (type: TaskType) => { switch (type) { - case TaskType.DATA_ARCHIVE: - case TaskType.DATA_DELETE: - case TaskType.LOGICAL_DATABASE_CHANGE: - case TaskType.PARTITION_PLAN: + case TaskType.LOGICAL_DATABASE_CHANGE: { return { - [TaskExecStrategy.TIMER]: formatMessage({ - id: 'odc.src.component.Task.CycleExecution', - defaultMessage: '周期执行', - }), //'周期执行' - [TaskExecStrategy.CRON]: formatMessage({ - id: 'odc.src.component.Task.CycleExecution.1', - defaultMessage: '周期执行', - }), //'周期执行' - [TaskExecStrategy.DAY]: formatMessage({ - id: 'odc.src.component.Task.CycleExecution.2', - defaultMessage: '周期执行', - }), //'周期执行' - [TaskExecStrategy.MONTH]: formatMessage({ - id: 'odc.src.component.Task.CycleExecution.3', - defaultMessage: '周期执行', - }), //'周期执行' - [TaskExecStrategy.WEEK]: formatMessage({ - id: 'odc.src.component.Task.CycleExecution.4', - defaultMessage: '周期执行', - }), //'周期执行' - [TaskExecStrategy.START_NOW]: formatMessage({ - id: 'odc.src.component.Task.ExecuteImmediately', + [TaskExecStrategy.AUTO]: formatMessage({ + id: 'odc.DataClearTask.CreateModal.ExecuteNow', defaultMessage: '立即执行', - }), //'立即执行' - [TaskExecStrategy.START_AT]: formatMessage({ - id: 'odc.src.component.Task.TimedExecution', + }), + [TaskExecStrategy.MANUAL]: formatMessage({ + id: 'src.component.Task.0B2B1D60', + defaultMessage: '手动执行', + }), //'手动执行' + [TaskExecStrategy.TIMER]: formatMessage({ + id: 'odc.DataClearTask.CreateModal.ScheduledExecution', defaultMessage: '定时执行', - }), //'定时执行' + }), }; + } case TaskType.STRUCTURE_COMPARISON: { return { [TaskExecStrategy.AUTO]: formatMessage({ @@ -211,3 +195,73 @@ export const getTaskExecStrategyTextMap = { defaultMessage: '手动执行', }), //'手动执行' }; + +const _commonActions = [TaskActionsEnum.SHARE, TaskActionsEnum.VIEW, TaskActionsEnum.CLONE]; + +const _AsyncTaskActions = [TaskActionsEnum.DOWNLOAD_VIEW_RESULT, TaskActionsEnum.VIEW_RESULT]; + +export const TaskStatus2Actions: Partial> = { + [TaskStatus.REJECTED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.APPROVAL_EXPIRED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.WAIT_FOR_EXECUTION_EXPIRED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.EXECUTION_EXPIRED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.CREATED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.EXECUTION_FAILED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.ROLLBACK_FAILED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.ROLLBACK_SUCCEEDED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.CANCELLED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.PRE_CHECK_FAILED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.COMPLETED]: [..._commonActions, ..._AsyncTaskActions], + [TaskStatus.EXECUTING]: [ + ..._commonActions, + ..._AsyncTaskActions, + TaskActionsEnum.DOWNLOAD_SQL, + TaskActionsEnum.STRUCTURE_COMPARISON, + TaskActionsEnum.STOP, + ], + [TaskStatus.EXECUTION_SUCCEEDED]: [ + ..._commonActions, + ..._AsyncTaskActions, + TaskActionsEnum.OPEN_LOCAL_FOLDER, + TaskActionsEnum.DOWNLOAD, + TaskActionsEnum.ROLLBACK, + TaskActionsEnum.DOWNLOAD_SQL, + TaskActionsEnum.STRUCTURE_COMPARISON, + ], + [TaskStatus.EXECUTION_SUCCEEDED_WITH_ERRORS]: [ + ..._commonActions, + ..._AsyncTaskActions, + TaskActionsEnum.OPEN_LOCAL_FOLDER, + TaskActionsEnum.DOWNLOAD, + TaskActionsEnum.ROLLBACK, + TaskActionsEnum.DOWNLOAD_SQL, + TaskActionsEnum.STRUCTURE_COMPARISON, + ], + [TaskStatus.WAIT_FOR_CONFIRM]: [ + ..._commonActions, + ..._AsyncTaskActions, + TaskActionsEnum.PASS, + TaskActionsEnum.REJECT, + TaskActionsEnum.STOP, + ], + [TaskStatus.APPROVING]: [ + ..._commonActions, + ..._AsyncTaskActions, + + TaskActionsEnum.PASS, + TaskActionsEnum.REJECT, + TaskActionsEnum.STOP, + ], + [TaskStatus.WAIT_FOR_EXECUTION]: [ + ..._commonActions, + ..._AsyncTaskActions, + TaskActionsEnum.EXECUTE, + TaskActionsEnum.STOP, + ], + [TaskStatus.EXECUTION_ABNORMAL]: [ + ..._commonActions, + ..._AsyncTaskActions, + TaskActionsEnum.STOP, + TaskActionsEnum.AGAIN, + ], +}; diff --git a/src/component/Task/container/Content.tsx b/src/component/Task/container/Content.tsx deleted file mode 100644 index 6a0213c1b..000000000 --- a/src/component/Task/container/Content.tsx +++ /dev/null @@ -1,341 +0,0 @@ -/* - * 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 { getCycleTaskDetail, getTaskDetail } from '@/common/network/task'; -import type { ITableInstance, ITableLoadOptions } from '@/component/CommonTable/interface'; -import type { IAlterScheduleTaskParams, TaskRecordParameters } from '@/d.ts'; -import { - IConnectionType, - ICycleTaskRecord, - TaskExecStrategy, - TaskPageType, - TaskRecord, - TaskType, -} from '@/d.ts'; -import { ModalStore } from '@/store/modal'; -import type { TaskStore } from '@/store/task'; -import tracert from '@/util/tracert'; -import { useLocation } from '@umijs/max'; -import { useSetState } from 'ahooks'; -import { message } from 'antd'; -import { inject, observer } from 'mobx-react'; -import type { Dayjs } from 'dayjs'; -import React, { useEffect, useRef, useMemo } from 'react'; -import TaskTable from '../component/TaskTable'; -import { isCycleTask, isCycleTaskPage } from '../helper'; -import styles from '../index.less'; -import { UserStore } from '@/store/login'; -import { TaskDetailContext } from './TaskDetailContext'; -import useURLParams from '@/util/hooks/useUrlParams'; -import { IState } from '../interface'; -import CreateModals from '../modals/CreateModals'; -import DetailModals from '../modals/DetailModals'; -import { formatMessage } from '@/util/intl'; -import { getPreTime } from '@/util/utils'; - -interface IProps { - taskStore?: TaskStore; - userStore?: UserStore; - modalStore?: ModalStore; - pageKey?: TaskPageType; - tabHeight?: number; - projectId?: number; - isMultiPage?: boolean; - inProject?: boolean; - defaultTaskId?: number; - defaultTaskType?: TaskType; -} - -const TaskManaerContent: React.FC = (props) => { - const { - pageKey, - taskStore, - modalStore, - isMultiPage = false, - inProject, - projectId, - userStore, - } = props; - const taskTabType = pageKey || taskStore?.taskPageType; - const taskOpenRef = useRef(null); - const [state, setState] = useSetState({ - detailId: props.taskStore?.defaultOpenTaskId, - detailType: props.taskStore?.defauleOpenTaskType, - detailVisible: !!props.taskStore?.defaultOpenTaskId, - tasks: null, - cycleTasks: null, - status: null, - }); - const location = useLocation(); - const isSqlworkspace = location?.pathname?.includes('/sqlworkspace'); - const { detailId, detailType, detailVisible, cycleTasks, tasks } = state; - const taskList = isCycleTaskPage(taskTabType) ? cycleTasks : tasks; - const theme = isSqlworkspace ? null : 'vs'; - const tableRef = useRef(); - const { getParam } = useURLParams(); - const urlTriggerValue = getParam('filtered'); - - const taskModalActions = { - [TaskPageType.IMPORT]: () => modalStore.changeImportModal(true), - [TaskPageType.EXPORT]: () => modalStore.changeExportModal(), - [TaskPageType.DATAMOCK]: () => modalStore.changeDataMockerModal(true), - [TaskPageType.ASYNC]: () => modalStore.changeCreateAsyncTaskModal(true), - [TaskPageType.PARTITION_PLAN]: () => modalStore.changePartitionModal(true), - [TaskPageType.SQL_PLAN]: () => modalStore.changeCreateSQLPlanTaskModal(true), - [TaskPageType.SHADOW]: () => modalStore.changeShadowSyncVisible(true), - [TaskPageType.DATA_ARCHIVE]: () => modalStore.changeDataArchiveModal(true), - [TaskPageType.STRUCTURE_COMPARISON]: () => modalStore.changeStructureComparisonModal(true), - [TaskPageType.DATA_DELETE]: () => modalStore.changeDataClearModal(true), - [TaskPageType.ONLINE_SCHEMA_CHANGE]: () => modalStore.changeCreateDDLAlterTaskModal(true), - [TaskPageType.EXPORT_RESULT_SET]: () => modalStore.changeCreateResultSetExportTaskModal(true), - [TaskPageType.APPLY_PROJECT_PERMISSION]: () => modalStore.changeApplyPermissionModal(true), - [TaskPageType.APPLY_DATABASE_PERMISSION]: () => - modalStore.changeApplyDatabasePermissionModal(true), - [TaskPageType.APPLY_TABLE_PERMISSION]: () => modalStore.changeApplyTablePermissionModal(true), - [TaskPageType.MULTIPLE_ASYNC]: () => - modalStore.changeMultiDatabaseChangeModal( - true, - inProject - ? { - projectId, - } - : null, - ), - [TaskPageType.LOGICAL_DATABASE_CHANGE]: () => modalStore.changeLogicialDatabaseModal(true), - }; - - const loadList = async (args: ITableLoadOptions, executeDate: [Dayjs, Dayjs]) => { - const { pageKey, taskStore } = props; - const taskTabType = pageKey || taskStore?.taskPageType; - if (isCycleTaskPage(taskTabType)) { - await loadCycleTaskList(taskTabType, args, executeDate); - } else { - await loadTaskList(taskTabType, args, executeDate); - } - }; - - const getListParams = (taskTabType, args: ITableLoadOptions, executeDate: [Dayjs, Dayjs]) => { - const { projectId } = props; - const { filters, sorter } = args ?? {}; - const { status, candidateApprovers, creator, projectIdList, executeTime } = filters ?? {}; - const { column, order } = sorter ?? {}; - - const isAllScope = ![ - TaskPageType.CREATED_BY_CURRENT_USER, - TaskPageType.APPROVE_BY_CURRENT_USER, - ].includes(taskTabType); - const isAll = taskTabType === TaskPageType.ALL; - - const commonParams = { - projectId: projectId || projectIdList || undefined, - status, - startTime: executeDate?.length > 0 ? executeDate?.[0]?.valueOf() ?? getPreTime(7) : undefined, - endTime: executeDate?.length > 0 ? executeDate?.[1]?.valueOf() ?? getPreTime(0) : undefined, - candidateApprovers, - creator, - sort: column?.dataIndex, - containsAll: isAll || isAllScope, - }; - if (executeTime !== 'custom' && typeof executeTime === 'number') { - commonParams.startTime = getPreTime(executeTime); - commonParams.endTime = getPreTime(0); - } - const sort = column ? `${column.dataIndex},${order === 'ascend' ? 'asc' : 'desc'}` : undefined; - return { commonParams, sort, isAllScope, isAll }; - }; - - const loadTaskList = async ( - taskTabType, - args: ITableLoadOptions, - executeDate: [Dayjs, Dayjs], - ) => { - const { filters, pageSize, pagination } = args ?? {}; - const { connection, id } = filters ?? {}; - const connectionId = connection?.filter( - (key) => ![IConnectionType.PRIVATE, IConnectionType.ORGANIZATION].includes(key), - ); - const { current = 1 } = pagination ?? {}; - - const { commonParams, sort, isAll, isAllScope } = getListParams(taskTabType, args, executeDate); - - if (!pageSize) { - return; - } - const params = { - ...commonParams, - fuzzySearchKeyword: id ? id : undefined, - taskType: isAllScope ? (isAll ? undefined : taskTabType) : undefined, - connectionId, - page: current, - size: pageSize, - createdByCurrentUser: isAllScope - ? true - : taskTabType === TaskPageType.CREATED_BY_CURRENT_USER, - approveByCurrentUser: isAllScope - ? true - : taskTabType === TaskPageType.APPROVE_BY_CURRENT_USER, - }; - // sorter - params.sort = sort; - - const tasks = await props.taskStore.getTaskList(params); - setState({ - tasks, - }); - }; - const loadCycleTaskList = async ( - taskTabType, - args: ITableLoadOptions, - executeDate: [Dayjs, Dayjs], - ) => { - const { filters, pageSize, pagination } = args ?? {}; - const { id } = filters ?? {}; - const { current = 1 } = pagination ?? {}; - - const { commonParams, sort, isAll, isAllScope } = getListParams(taskTabType, args, executeDate); - - if (!pageSize) { - return; - } - const params = { - ...commonParams, - id: id ? id : undefined, - type: isAllScope ? (isAll ? undefined : taskTabType) : undefined, - page: urlTriggerValue ? undefined : current, - size: urlTriggerValue ? undefined : pageSize, - createdByCurrentUser: taskTabType === TaskPageType.CREATED_BY_CURRENT_USER, - approveByCurrentUser: taskTabType === TaskPageType.APPROVE_BY_CURRENT_USER, - }; - // sorter - params.sort = sort; - - const cycleTasks = await props.taskStore.getCycleTaskList(params); - const filteredContents = cycleTasks?.contents?.filter( - (item) => - ![TaskExecStrategy.START_NOW, TaskExecStrategy.START_AT].includes( - item?.triggerConfig?.triggerStrategy, - ), - ); - setState({ - cycleTasks: urlTriggerValue ? { ...cycleTasks, contents: filteredContents } : cycleTasks, - }); - }; - - const reloadList = () => { - tableRef.current.reload(); - }; - - const handleDetailVisible = ( - task: TaskRecord | ICycleTaskRecord, - visible: boolean = false, - ) => { - const { id, type } = task ?? {}; - const detailId = - type === TaskType.ALTER_SCHEDULE - ? (task as TaskRecord)?.parameters?.taskId - : id; - setState({ - detailId, - detailType: - (task as TaskRecord)?.type || - (task as ICycleTaskRecord)?.type || - TaskType.ASYNC, - detailVisible: visible, - }); - taskOpenRef.current = visible; - }; - - const handleMenuItemClick = (type: TaskPageType) => { - tracert.click('a3112.b64006.c330917.d367464', { - type, - }); - taskModalActions?.[type]?.(); - }; - - const openDefaultTask = async () => { - const { defaultTaskId, defaultTaskType } = props; - if (defaultTaskId) { - const isSchedule = isCycleTask(defaultTaskType); - const data = isSchedule - ? await getCycleTaskDetail(defaultTaskId) - : await getTaskDetail(defaultTaskId, true); - if (!data) { - message.error( - formatMessage({ - id: 'odc.src.component.Task.NoCurrentWorkOrderView', - defaultMessage: '无当前工单查看权限', - }), //'无当前工单查看权限' - ); - return; - } - setState({ - detailId: defaultTaskId, - detailType: defaultTaskType || TaskType.ASYNC, - detailVisible: true, - }); - taskOpenRef.current = true; - } - }; - - useEffect(() => { - openDefaultTask(); - }, []); - - /** - * 隐藏项目列 - * 隐藏:项目中工单、个人空间 - * 显示:sql控制台 - */ - const disableProjectCol = useMemo(() => { - if (inProject || userStore.isPrivateSpace()) { - return true; - } - return false; - }, [inProject, userStore.organizationId]); - - return ( - -
- -
- - -
- ); -}; -export default inject('userStore', 'taskStore', 'modalStore')(observer(TaskManaerContent)); diff --git a/src/component/Task/container/Sider.tsx b/src/component/Task/container/Sider.tsx deleted file mode 100644 index 49dbb5233..000000000 --- a/src/component/Task/container/Sider.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 { TaskPageType } from '@/d.ts'; -import { openTasksPage } from '@/store/helper/page'; -import type { PageStore } from '@/store/page'; -import { TaskStore } from '@/store/task'; -import Icon from '@ant-design/icons'; -import { Space, Tooltip, Typography } from 'antd'; -import classNames from 'classnames'; -import { inject, observer } from 'mobx-react'; -import React, { useEffect } from 'react'; -import { getFirstEnabledTask, getTaskGroupLabels } from '../helper'; -import useUrlAction from '@/util/hooks/useUrlAction'; -import useURLParams from '@/util/hooks/useUrlParams'; -import styles from '../index.less'; - -interface IProps { - taskStore?: TaskStore; - pageStore?: PageStore; - className?: string; - isPage?: boolean; - inProject?: boolean; -} - -const { Text } = Typography; - -const Sider: React.FC = function ({ taskStore, pageStore, className, isPage }) { - const firstEnabledTask = getFirstEnabledTask(); - const pageKey = isPage ? pageStore?.activePageKey : taskStore?.taskPageType; - const { getParam, deleteParam } = useURLParams(); - const urlTriggerValue = getParam('filtered'); - const urlStatusValue = getParam('status'); - - const { runTask } = useUrlAction(); - - const handleClick = (value: TaskPageType) => { - if (urlTriggerValue) { - deleteParam('filtered'); - } - if (urlStatusValue) { - deleteParam('status'); - } - if (isPage) { - openTasksPage(value); - } - taskStore.changeTaskPageType(value); - taskStore.setSelectedRowKeys([]); - }; - - function renderTaskTypeList() { - return getTaskGroupLabels() - ?.map((taskGroup) => { - const { groupName, icon, group } = taskGroup; - const tasks = group?.filter((task) => task.enabled); - const groupIcon = icon ? : null; - if (!tasks?.length) { - return null; - } - return ( -
- {groupName ? ( - - {groupIcon} - {groupName} - - ) : null} - {tasks.map((item) => { - return ( -
handleClick(item.value)} - > - - {item.label} - -
- ); - })} -
- ); - }) - .filter(Boolean); - } - - useEffect(() => { - const res = runTask({ - callback: (task) => { - openTasksPage(task as TaskPageType); - taskStore.changeTaskPageType(task as TaskPageType); - }, - }); - - if (!res) taskStore.changeTaskPageType(firstEnabledTask?.value); - return () => { - taskStore.changeTaskPageType(firstEnabledTask?.value); - taskStore.changeTaskPageScope(null); - }; - }, []); - - return
{renderTaskTypeList()}
; -}; - -export default inject('taskStore', 'pageStore')(observer(Sider)); diff --git a/src/component/Task/container/TaskDetailContext.tsx b/src/component/Task/container/TaskDetailContext.tsx deleted file mode 100644 index 26bc96f88..000000000 --- a/src/component/Task/container/TaskDetailContext.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { ICycleTaskRecord, TaskRecord, TaskRecordParameters } from '@/d.ts'; -import React from 'react'; -import { IState } from '../interface'; - -interface ITaskDetailContext { - handleDetailVisible: ( - task: TaskRecord | ICycleTaskRecord, - visible?: boolean, - ) => void; - setState: (patch: Partial | ((prevState: IState) => Partial)) => void; -} - -export const TaskDetailContext = React.createContext(null); diff --git a/src/component/Task/context/ParamsContext.tsx b/src/component/Task/context/ParamsContext.tsx new file mode 100644 index 000000000..89bbdb0f1 --- /dev/null +++ b/src/component/Task/context/ParamsContext.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { IProject } from '@/d.ts/project'; +import { ITaskParam, TaskPageMode, TaskTab } from '@/component/Task/interface'; +import { IScheduleParam } from '@/component/Schedule/interface'; +import { SchedulePageType } from '@/d.ts/schedule'; + +export const defaultParam: ITaskParam = { + searchValue: undefined, + searchType: undefined, + taskTypes: [], + taskStatus: [], + projectId: [], + sort: '', + tab: TaskTab.all, + timeRange: 7, + executeDate: [undefined, undefined], +}; + +interface IParamsContext { + params?: ITaskParam; + setParams?: ( + patch: Partial | ((prevState: ITaskParam) => Partial), + ) => void; + projectList: IProject[]; + reload?: () => void; + scheduleTabType?: SchedulePageType; + mode?: TaskPageMode; +} + +const ParamsContext: React.Context = React.createContext({ + projectList: [], +}); + +export default ParamsContext; diff --git a/src/component/Task/context/TaskDetailContext.tsx b/src/component/Task/context/TaskDetailContext.tsx new file mode 100644 index 000000000..7b4bba351 --- /dev/null +++ b/src/component/Task/context/TaskDetailContext.tsx @@ -0,0 +1,10 @@ +import type { TaskRecord, TaskRecordParameters } from '@/d.ts'; +import React from 'react'; +import { IState } from '@/component/Task/interface'; + +interface ITaskDetailContext { + handleDetailVisible: (task: TaskRecord, visible?: boolean) => void; + setState: (patch: Partial | ((prevState: IState) => Partial)) => void; +} + +export const TaskDetailContext = React.createContext(null); diff --git a/src/component/Task/helper.tsx b/src/component/Task/helper.tsx index 65b850e11..7f293bfb6 100644 --- a/src/component/Task/helper.tsx +++ b/src/component/Task/helper.tsx @@ -20,11 +20,14 @@ import login from '@/store/login'; import settingStore from '@/store/setting'; import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; -import { getPreTime } from '@/util/utils'; import { flatten } from 'lodash'; -import type { Dayjs } from 'dayjs'; -import { ITableLoadOptions } from '../CommonTable/interface'; export { TaskTypeMap } from '@/component/Task/component/TaskTable/const'; +import { TaskConfig, allTaskPageConfig } from '@/common/task'; +import { ITaskParam, TaskTab } from './interface'; +import { + TASK_EXECUTE_DATE_KEY, + TASK_EXECUTE_TIME_KEY, +} from '@/component/Task/component/TaskTable/const'; import dayjs from 'dayjs'; // 423 屏蔽 SysFormItem 配置 @@ -44,14 +47,6 @@ export const hasPermission = (taskType: TaskType, permissions: DatabasePermissio return _permissions.every((item) => permissions?.includes(item)); }; -export const isCycleTask = (type: TaskType) => { - return [ - TaskType.LOGICAL_DATABASE_CHANGE, - TaskType.SQL_PLAN, - TaskType.DATA_ARCHIVE, - TaskType.DATA_DELETE, - ].includes(type); -}; export const isLogicalDbChangeTask = (type: TaskType) => TaskType.LOGICAL_DATABASE_CHANGE === type; export const isCycleTriggerStrategy = (execStrategy: TaskExecStrategy) => { return [ @@ -62,246 +57,6 @@ export const isCycleTriggerStrategy = (execStrategy: TaskExecStrategy) => { TaskExecStrategy.TIMER, ].includes(execStrategy); }; -export const isSubCycleTask = (type: SubTaskType) => { - return [ - SubTaskType.DATA_ARCHIVE, - SubTaskType.DATA_ARCHIVE_ROLLBACK, - SubTaskType.DATA_DELETE, - ].includes(type); -}; -export const isCycleTaskPage = (type: TaskPageType) => { - return [ - TaskPageType.SQL_PLAN, - TaskPageType.DATA_ARCHIVE, - TaskPageType.DATA_DELETE, - TaskPageType.LOGICAL_DATABASE_CHANGE, - ].includes(type); -}; - -interface ITaskGroupLabel { - groupName: string; - icon?: React.ReactNode; - group: { - value: TaskPageType; - label: string; - enabled: boolean; - }[]; -} -export const getTaskGroupLabels: () => ITaskGroupLabel[] = () => { - const isPersonal = login?.isPrivateSpace(); - return [ - { - groupName: '', - group: [ - { - label: formatMessage({ - id: 'odc.src.component.Task.AllWorkOrders', - defaultMessage: '所有工单', - }), //'所有工单' - value: TaskPageType.ALL, - enabled: !isClient(), - }, - { - label: formatMessage({ - id: 'odc.component.TaskPopover.IInitiated', - defaultMessage: '我发起的', - }), - value: TaskPageType.CREATED_BY_CURRENT_USER, - enabled: !isClient(), - }, - { - label: formatMessage({ - id: 'odc.component.TaskPopover.PendingMyApproval', - defaultMessage: '待我审批', - }), - value: TaskPageType.APPROVE_BY_CURRENT_USER, - enabled: !isClient() && !isPersonal, - }, - ], - }, - { - groupName: formatMessage({ - id: 'odc.component.Task.helper.DataExport', - defaultMessage: '数据导出', - }), - //数据导出 - group: [ - { - value: TaskPageType.EXPORT, - label: formatMessage({ - id: 'odc.components.TaskManagePage.Export', - defaultMessage: '导出', - }), - // 导出 - enabled: settingStore.enableDBExport, - }, - { - value: TaskPageType.EXPORT_RESULT_SET, - label: formatMessage({ - id: 'odc.src.component.Task.ExportResultSet', - defaultMessage: '导出结果集', - }), - //'导出结果集' - enabled: settingStore.enableDBExport, - }, - ], - }, - { - groupName: formatMessage({ - id: 'odc.component.Task.helper.DataChanges', - defaultMessage: '数据变更', - }), - //数据变更 - group: [ - { - value: TaskPageType.IMPORT, - label: formatMessage({ - id: 'odc.components.TaskManagePage.Import', - defaultMessage: '导入', - }), - // 导入 - enabled: settingStore.enableDBImport, - }, - { - value: TaskPageType.DATAMOCK, - label: formatMessage({ - id: 'odc.components.TaskManagePage.AnalogData', - defaultMessage: '模拟数据', - }), - // 模拟数据 - enabled: settingStore.enableMockdata, - }, - { - value: TaskPageType.ASYNC, - label: formatMessage({ - id: 'odc.components.TaskManagePage.DatabaseChanges', - defaultMessage: '数据库变更', - }), - enabled: settingStore.enableAsyncTask, - // 数据库变更 - }, - { - value: TaskPageType.MULTIPLE_ASYNC, - label: formatMessage({ id: 'src.component.Task.1EDC83CC', defaultMessage: '多库变更' }), - enabled: settingStore.enableMultipleAsyncTask, - // 数据库变更 - }, - { - value: TaskPageType.LOGICAL_DATABASE_CHANGE, - label: formatMessage({ id: 'src.component.Task.A7954C70', defaultMessage: '逻辑库变更' }), - enabled: !login.isPrivateSpace() && settingStore?.enableLogicaldatabase, - }, - { - value: TaskPageType.SHADOW, - label: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTable.ShadowTableSynchronization', - defaultMessage: '影子表同步', - }), - //影子表同步 - enabled: settingStore.enableShadowTableSync, - }, - { - value: TaskPageType.STRUCTURE_COMPARISON, - label: formatMessage({ id: 'src.component.Task.223677D8', defaultMessage: '结构比对' }), //'结构比对' - - enabled: settingStore.enableStructureCompare, - }, - { - value: TaskPageType.ONLINE_SCHEMA_CHANGE, - label: formatMessage({ - id: 'odc.component.Task.helper.LockFreeStructureChange', - defaultMessage: '无锁结构变更', - }), - //无锁结构变更 - enabled: settingStore.enableOSC, - }, - ], - }, - { - groupName: formatMessage({ - id: 'odc.component.Task.helper.ScheduledTasks', - defaultMessage: '定时任务', - }), - //定时任务 - group: [ - { - value: TaskPageType.SQL_PLAN, - label: formatMessage({ - id: 'odc.TaskManagePage.component.helper.SqlPlan', - defaultMessage: 'SQL 计划', - }), - //SQL 计划 - enabled: settingStore.enableSQLPlan, - }, - { - value: TaskPageType.PARTITION_PLAN, - label: formatMessage({ - id: 'odc.TaskManagePage.component.TaskTable.PartitionPlan', - defaultMessage: '分区计划', - }), - enabled: settingStore.enablePartitionPlan, - }, - { - value: TaskPageType.DATA_ARCHIVE, - label: formatMessage({ - id: 'odc.component.Task.helper.DataArchiving', - defaultMessage: '数据归档', - }), - //数据归档 - enabled: settingStore.enableDataArchive, - }, - { - value: TaskPageType.DATA_DELETE, - label: formatMessage({ - id: 'odc.component.Task.helper.DataCleansing', - defaultMessage: '数据清理', - }), - //数据清理 - enabled: settingStore.enableDataClear, - }, - ], - }, - { - groupName: formatMessage({ - id: 'odc.src.component.Task.AccessRequest', - defaultMessage: '权限申请', - }), //'权限申请' - group: [ - { - value: TaskPageType.APPLY_DATABASE_PERMISSION, - label: formatMessage({ id: 'src.component.Task.F2EE6904', defaultMessage: '申请库权限' }), //'申请库权限' - enabled: settingStore.enableApplyDBAuth, - }, - { - value: TaskPageType.APPLY_PROJECT_PERMISSION, - label: formatMessage({ - id: 'odc.src.component.Task.ApplicationProjectPermissions', - defaultMessage: '申请项目权限', - }), //'申请项目权限' - enabled: settingStore.enableApplyProjectAuth, - }, - { - value: TaskPageType.APPLY_TABLE_PERMISSION, - label: formatMessage({ - id: 'src.component.Task.7FE73181', - defaultMessage: '申请表/视图权限', - }), - enabled: settingStore.enableApplyTableAuth, - }, - ], - }, - ]; -}; - -export function getTaskLabels() { - return flatten(getTaskGroupLabels()?.map((item) => item?.group)); -} -export function getFirstEnabledTask() { - return getTaskLabels()?.find((item) => item?.enabled); -} -export function getTaskLabelByType(type: TaskPageType) { - return getTaskLabels()?.find((item) => item.value === type)?.label; -} export const conditionExpressionColumns = [ { @@ -323,6 +78,31 @@ export const conditionExpressionColumns = [ }, ]; +export const getFirstEnabledTask = () => { + return [allTaskPageConfig, ...Object.values(TaskConfig)]?.find((item) => item.enabled()); +}; + +export const getDefaultParam: () => ITaskParam = () => { + const _defaultParam: ITaskParam = { + searchValue: undefined, + searchType: undefined, + taskTypes: [], + taskStatus: [], + projectId: [], + sort: '', + tab: TaskTab.all, + timeRange: 7, + executeDate: [undefined, undefined], + }; + _defaultParam.timeRange = + JSON.parse(localStorage.getItem(TASK_EXECUTE_TIME_KEY)) ?? _defaultParam.timeRange; + const [start, end] = JSON.parse(localStorage?.getItem(TASK_EXECUTE_DATE_KEY)) ?? [null, null]; + if (!!start && !!end) { + _defaultParam.executeDate = [dayjs(start), dayjs(end)]; + } + return _defaultParam; +}; + type TimeUnit = 'years' | 'months' | 'days'; const MAX_DATE = '9999-12-31 23:59:59'; diff --git a/src/component/Task/hooks/index.tsx b/src/component/Task/hooks/index.tsx new file mode 100644 index 000000000..155dfd460 --- /dev/null +++ b/src/component/Task/hooks/index.tsx @@ -0,0 +1,4 @@ +import useTaskGroup from './useTaskGroup'; +import useLoadProjects from './useLoadProjects'; + +export { useTaskGroup, useLoadProjects }; diff --git a/src/component/Task/hooks/useLoadProjects.tsx b/src/component/Task/hooks/useLoadProjects.tsx index 6858edadc..885b34f27 100644 --- a/src/component/Task/hooks/useLoadProjects.tsx +++ b/src/component/Task/hooks/useLoadProjects.tsx @@ -18,7 +18,7 @@ import { useState } from 'react'; import { getProjectList } from '@/common/network/task'; import { IProject } from '@/d.ts/project'; -export const useLoadProjects = () => { +const useLoadProjects = () => { const [projects, setProjects] = useState([]); const [projectMap, setProjectMap] = useState>({}); const projectOptions = projects?.map(({ name, id }) => ({ @@ -43,3 +43,5 @@ export const useLoadProjects = () => { loadProjects, }; }; + +export default useLoadProjects; diff --git a/src/component/Task/hooks/useTaskGroup.tsx b/src/component/Task/hooks/useTaskGroup.tsx new file mode 100644 index 000000000..a59979bdf --- /dev/null +++ b/src/component/Task/hooks/useTaskGroup.tsx @@ -0,0 +1,39 @@ +import type { ITaskConfig } from '@/common/task'; +import { TaskGroup } from '@/d.ts/task'; +import { useMemo } from 'react'; +import { TaskGroupTextMap } from '@/constant/task'; +import { TaskPageType } from '@/d.ts'; + +const useTaskGroup = (props: { taskItems: ITaskConfig[] }) => { + const { taskItems } = props; + + const results = useMemo(() => { + const _results: Array<{ + label: string; + key: TaskGroup; + children: Array<{ label: string; value: TaskPageType }>; + }> = Object.values(TaskGroup).map((item) => ({ + label: TaskGroupTextMap[item], + key: item, + children: [], + })); + _results.forEach((at) => { + taskItems.forEach((item) => { + if (!item.enabled()) { + return; + } + if (item.groupBy === at.key) { + at.children.push({ + label: item.label, + value: item.pageType, + }); + } + }); + }); + return _results.filter((item) => item.children.length); + }, [taskItems]); + + return { results }; +}; + +export default useTaskGroup; diff --git a/src/component/Task/index.less b/src/component/Task/index.less index 45c02c289..ea932b9f7 100644 --- a/src/component/Task/index.less +++ b/src/component/Task/index.less @@ -12,8 +12,15 @@ line-height: 24px; opacity: 0.85; } - :global { + .ant-spin-nested-loading { + height: 100%; + .ant-spin-container { + .ant-table-wrapper { + height: 100%; + } + } + } .ant-input-search { width: 178px; } @@ -35,6 +42,10 @@ } } } +.hoverLink:hover { + color: #1890ff !important; + cursor: pointer; +} .tools { :global { @@ -187,3 +198,49 @@ background-color: var(--hover-color); } } + +.batchOperationDropdown { + :global { + .ant-dropdown-menu { + .ant-dropdown-menu-item { + padding: 0; + .ant-dropdown-menu-title-content { + .ant-btn { + width: 100%; + justify-content: left; + } + } + } + } + } +} + +.taskNameColumn { + color: var(--text-color-secondary); + padding: 5px 0px 5px 6px; + .columns { + display: flex; + gap: 8px; + } + .taskName { + font-weight: 600; + color: black; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .creator { + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + } + .project { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + } +} diff --git a/src/component/Task/index.tsx b/src/component/Task/index.tsx index a619ee75f..5d72a837b 100644 --- a/src/component/Task/index.tsx +++ b/src/component/Task/index.tsx @@ -18,36 +18,38 @@ import { TaskType } from '@/d.ts'; import login from '@/store/login'; import { useSearchParams } from '@umijs/max'; import { toInteger } from 'lodash'; -import Content from './container/Content'; +import Content from './layout/Content'; import styles from './index.less'; -import Sider from './container/Sider'; +import Sider from './layout/Sider'; +import { TaskPageMode } from './interface'; +import CreateModals from '@/component/Task/modals/CreateModals'; interface IProps { projectId?: number; - inProject?: boolean; + mode: TaskPageMode; } const TaskManagerPage: React.FC = (props) => { - const { projectId, inProject } = props; + const { projectId, mode } = props; const [search] = useSearchParams(); const defaultTaskId = search.get('taskId'); const defaultTaskType = search.get('taskType') as TaskType; const defaultOrganizationId = search.get('organizationId'); const currentOrganizationId = login.organizationId; const isOrganizationMatch = toInteger(defaultOrganizationId) === toInteger(currentOrganizationId); + return ( - <> -
-
- -
- +
+
+
- + + +
); }; export default TaskManagerPage; diff --git a/src/component/Task/interface.ts b/src/component/Task/interface.ts index 6cafb48d5..7ad1a5e57 100644 --- a/src/component/Task/interface.ts +++ b/src/component/Task/interface.ts @@ -25,20 +25,16 @@ import type { IResponseData, Operation, TaskStatus, - ICycleTaskRecord, - ISqlPlayJobParameters, - IDataArchiveJobParameters, TaskType, } from '@/d.ts'; +import type { Dayjs } from 'dayjs'; -export interface IState { - detailId: number; - detailType: TaskType; - detailVisible: boolean; - status: TaskStatus; - tasks: IResponseData>; - cycleTasks: IResponseData>; +export enum TaskPageMode { + COMMON = 'COMMON', + PROJECT = 'PROJECT', + MULTI_PAGE = 'MULTI_PAGE', } + export interface ITaskDetailModalProps { visible: boolean; taskTools: React.ReactNode; @@ -46,8 +42,6 @@ export interface ITaskDetailModalProps { detailType: TaskDetailType; detailId: number; task: TaskDetail; - subTasks: IResponseData>; - opRecord: Operation[]; hasFlow: boolean; result: ITaskResult; log: ILog; @@ -59,12 +53,57 @@ export interface ITaskDetailModalProps { } export enum TaskDetailType { + /** 任务信息 */ INFO = 'info', + /** 任务流程 */ FLOW = 'flow', + /** 执行结果 */ RESULT = 'result', + /** 任务日志 */ LOG = 'log', + /** 回滚工单 */ RECORD = 'record', - EXECUTE_RECORD = 'execute_record', - OPERATION_RECORD = 'operation_record', + /** 执行记录(无锁结构变更、多库变更) */ PROGRESS = 'progress', } + +export interface IState { + detailId: number; + detailType: TaskType; + detailVisible: boolean; + status: TaskStatus; + tasks: IResponseData>; +} + +export enum TaskSearchType { + ID = 'ID', + DESCRIPTION = 'DESCRIPTION', + CREATOR = 'CREATOR_NAME', + DATABASE = 'DATABASE_NAME', + DATASOURCE = 'DATASOURCE_NAME', + CLUSTER = 'CLUSTER_NAME', + TENANT = 'TENANT_NAME', +} + +export enum TaskTab { + all = ' all', + executionByCurrentUser = 'executionByCurrentUser', + approveByCurrentUser = 'approveByCurrentUser', +} + +export interface ITaskParam { + searchValue: string; + searchType: TaskSearchType; + taskTypes: string[]; + taskStatus: string[]; + projectId: string[]; + sort: string; + timeRange: number | string; + executeDate?: [Dayjs, Dayjs]; + tab?: TaskTab; +} + +export interface IPagination { + current: number; + pageSize: number; +} diff --git a/src/component/Task/layout/Content.tsx b/src/component/Task/layout/Content.tsx new file mode 100644 index 000000000..2962f17f0 --- /dev/null +++ b/src/component/Task/layout/Content.tsx @@ -0,0 +1,264 @@ +import DetailModals from '@/component/Task/modals/DetailModals'; +import styles from '@/component/Task/index.less'; +import type { TaskStore } from '@/store/task'; +import { UserStore } from '@/store/login'; +import { ModalStore } from '@/store/modal'; +import { useLocation } from '@umijs/max'; +import type { ITableInstance } from '@/component/CommonTable/interface'; +import { useEffect, useRef, useState } from 'react'; +import { getPreTime } from '@/util/utils'; +import { getTaskDetail } from '@/common/network/task'; +import tracert from '@/util/tracert'; +import { formatMessage } from '@/util/intl'; +import { TaskDetailContext } from '@/component/Task/context/TaskDetailContext'; +import { useSetState } from 'ahooks'; +import { inject, observer } from 'mobx-react'; +import { + TaskPageType, + TaskRecord, + TaskRecordParameters, + TaskType, + IAlterScheduleTaskParams, + TaskStatus, +} from '@/d.ts'; +import { message } from 'antd'; +import TaskTable from '../component/TaskTable'; +import { IPagination, ITaskParam, TaskPageMode, TaskTab } from '@/component/Task/interface'; +import { IState } from '@/component/Task/interface'; +import ApprovalModal from '@/component/Task/component/ApprovalModal'; +import { getDefaultParam } from '../helper'; + +interface IProps { + taskStore?: TaskStore; + userStore?: UserStore; + modalStore?: ModalStore; + pageKey?: TaskPageType; + tabHeight?: number; + projectId?: number; + defaultTaskId?: number; + defaultTaskType?: TaskType; + mode?: TaskPageMode; +} + +const Content: React.FC = (props) => { + const { + pageKey, + taskStore, + modalStore, + projectId, + userStore, + mode = TaskPageMode.COMMON, + } = props; + const taskTabType = pageKey || taskStore?.taskPageType; + const location = useLocation(); + const isSqlworkspace = location?.pathname?.includes('/sqlworkspace'); + const theme = isSqlworkspace ? null : 'vs'; + const tableRef = useRef(); + const [loading, setLoading] = useState(false); + const [state, setState] = useSetState({ + detailId: taskStore?.defaultOpenTaskId, + detailType: taskStore?.defauleOpenTaskType, + detailVisible: !!taskStore?.defaultOpenTaskId, + tasks: null, + status: null, + }); + + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 0, + }); + const [params, setParams] = useSetState(getDefaultParam()); + + const [approvalState, setApprovalState] = useSetState({ + visible: false, + approvalStatus: false, + detailId: null, + }); + + const taskModalActions = { + [TaskPageType.IMPORT]: () => modalStore.changeImportModal(true), + [TaskPageType.EXPORT]: () => modalStore.changeExportModal(), + [TaskPageType.DATAMOCK]: () => modalStore.changeDataMockerModal(true), + [TaskPageType.ASYNC]: () => modalStore.changeCreateAsyncTaskModal(true), + [TaskPageType.SHADOW]: () => modalStore.changeShadowSyncVisible(true), + [TaskPageType.STRUCTURE_COMPARISON]: () => modalStore.changeStructureComparisonModal(true), + [TaskPageType.ONLINE_SCHEMA_CHANGE]: () => modalStore.changeCreateDDLAlterTaskModal(true), + [TaskPageType.EXPORT_RESULT_SET]: () => modalStore.changeCreateResultSetExportTaskModal(true), + [TaskPageType.APPLY_PROJECT_PERMISSION]: () => modalStore.changeApplyPermissionModal(true), + [TaskPageType.APPLY_DATABASE_PERMISSION]: () => + modalStore.changeApplyDatabasePermissionModal(true), + [TaskPageType.APPLY_TABLE_PERMISSION]: () => modalStore.changeApplyTablePermissionModal(true), + [TaskPageType.MULTIPLE_ASYNC]: () => + modalStore.changeMultiDatabaseChangeModal( + true, + mode === TaskPageMode.PROJECT + ? { + projectId, + } + : null, + ), + [TaskPageType.LOGICAL_DATABASE_CHANGE]: () => modalStore.changeLogicialDatabaseModal(true), + }; + + const resolveParams = (params: ITaskParam, pagination: IPagination) => { + const { + sort, + searchValue, + taskTypes, + taskStatus, + searchType, + projectId: projectIdList, + timeRange, + executeDate, + tab, + } = params ?? {}; + const { pageSize, current } = pagination ?? {}; + + const taskTabType = pageKey || taskStore?.taskPageType; + const isAll = taskTabType === TaskPageType.ALL; + const apiParams = { + fuzzySearchKeyword: searchValue, + searchType, + status: tab === TaskTab.executionByCurrentUser ? [TaskStatus.WAIT_FOR_EXECUTION] : taskStatus, + taskTypes: isAll ? taskTypes : taskTabType, + projectId: projectId || projectIdList || undefined, + startTime: timeRange === 'ALL' ? undefined : String(getPreTime(7)), + endTime: timeRange === 'ALL' ? undefined : String(getPreTime(0)), + sort, + page: current, + size: pageSize, + createdByCurrentUser: true, + approveByCurrentUser: [TaskTab.all, TaskTab.approveByCurrentUser].includes(tab), + containsAll: tab === TaskTab.all, + }; + if (typeof timeRange === 'number') { + apiParams.startTime = String(getPreTime(timeRange)); + apiParams.endTime = String(getPreTime(0)); + } + if (timeRange === 'custom' && executeDate?.filter(Boolean)?.length === 2) { + apiParams.startTime = String(executeDate?.[0]?.valueOf()); + apiParams.endTime = String(executeDate?.[1]?.valueOf()); + } + + return apiParams; + }; + + const loadTaskList = async (params: ITaskParam, pagination: IPagination) => { + const apiParams = resolveParams(params, pagination); + if (!apiParams.size) { + return; + } + const tasks = await props.taskStore.getTaskList(apiParams as any); + setLoading(false); + setState({ + tasks, + }); + }; + + const reloadList = () => { + loadTaskList(params, pagination); + }; + + const handleApprovalVisible = (approvalStatus: boolean = false, id: number) => { + setApprovalState({ + detailId: id, + approvalStatus, + visible: true, + }); + }; + + const handleDetailVisible = ( + task: TaskRecord, + visible: boolean = false, + ) => { + const { id, type } = task ?? {}; + const detailId = + type === TaskType.ALTER_SCHEDULE + ? (task as TaskRecord)?.parameters?.taskId + : id; + setState({ + detailId, + detailType: (task as TaskRecord)?.type || TaskType.ASYNC, + detailVisible: visible, + }); + }; + + const handleMenuItemClick = (type: TaskPageType) => { + tracert.click('a3112.b64006.c330917.d367464', { + type, + }); + taskModalActions?.[type]?.(); + }; + + const openDefaultTask = async () => { + const { defaultTaskId, defaultTaskType } = props; + if (defaultTaskId) { + const data = await getTaskDetail(defaultTaskId, true); + if (!data) { + message.error( + formatMessage({ + id: 'odc.src.component.Task.NoCurrentWorkOrderView', + defaultMessage: '无当前工单查看权限', + }), //'无当前工单查看权限' + ); + return; + } + setState({ + detailId: defaultTaskId, + detailType: defaultTaskType || TaskType.ASYNC, + detailVisible: true, + }); + } + }; + + useEffect(() => { + openDefaultTask(); + }, []); + + return ( + +
+ +
+ + setApprovalState({ visible: false })} + /> +
+ ); +}; + +export default inject('userStore', 'taskStore', 'modalStore')(observer(Content)); diff --git a/src/component/Task/layout/Header/DateSelect.tsx b/src/component/Task/layout/Header/DateSelect.tsx new file mode 100644 index 000000000..1cd551695 --- /dev/null +++ b/src/component/Task/layout/Header/DateSelect.tsx @@ -0,0 +1,103 @@ +import React, { useContext } from 'react'; +import { ClockCircleOutlined } from '@ant-design/icons'; +import { Select, DatePicker } from 'antd'; +import FilterIcon from '@/component/Button/FIlterIcon'; +import { formatMessage } from '@/util/intl'; +import ParamsContext from '@/component/Task/context/ParamsContext'; +import styles from './index.less'; +import dayjs, { Dayjs } from 'dayjs'; + +const { RangePicker } = DatePicker; + +export const TIME_OPTION_ALL_TASK = 'ALL'; + +export const TimeOptions = [ + { + label: formatMessage({ id: 'odc.component.TimeSelect.LastDays', defaultMessage: '最近 7 天' }), //最近 7 天 + value: 7, + }, + + { + label: formatMessage({ + id: 'odc.component.TimeSelect.LastDays.1', + defaultMessage: '最近 15 天', + }), //最近 15 天 + value: 15, + }, + + { + label: formatMessage({ + id: 'odc.component.TimeSelect.LastDays.2', + defaultMessage: '最近 30 天', + }), //最近 30 天 + value: 30, + }, + { + label: formatMessage({ + id: 'odc.component.TimeSelect.LastSixMonths', + defaultMessage: '最近半年', + }), //最近半年 + value: 183, + }, + { + label: formatMessage({ id: 'src.component.TimeSelect.9E6CA23B', defaultMessage: '全部' }), + value: TIME_OPTION_ALL_TASK, + }, + { + label: formatMessage({ id: 'odc.component.TimeSelect.Custom', defaultMessage: '自定义' }), //自定义 + value: 'custom', + }, +]; + +const DateSelect = () => { + const context = useContext(ParamsContext); + const { params, setParams } = context; + const { timeRange, executeDate } = params; + + const handleChange = (value) => { + setParams({ timeRange: value }); + }; + + const handleSelectDate = (value) => { + setParams({ executeDate: value }); + }; + + return ( + +
+ + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + value={projectId} + mode="multiple" + options={projectOptions} + style={{ width: '100%' }} + onChange={handleSelectProject} + allowClear + /> + + ); +}; + +export default ProjectFilter; diff --git a/src/component/Task/layout/Header/Filter/taskStatusFilter.tsx b/src/component/Task/layout/Header/Filter/taskStatusFilter.tsx new file mode 100644 index 000000000..1eeac2826 --- /dev/null +++ b/src/component/Task/layout/Header/Filter/taskStatusFilter.tsx @@ -0,0 +1,44 @@ +import ParamsContext from '@/component/Task/context/ParamsContext'; +import { useContext, useMemo } from 'react'; +import { Select } from 'antd'; +import { status } from '@/component/Task/component/Status'; +import { TaskStatus } from '@/d.ts'; +import { debounce } from 'lodash'; + +const TaskStatusFilter = () => { + const context = useContext(ParamsContext); + const { params, setParams } = context || {}; + const { taskStatus } = params || {}; + + const taskStatusFilters = useMemo(() => { + return Object.keys(status) + ?.filter((key) => key !== TaskStatus.WAIT_FOR_CONFIRM) + .map((key) => ({ + label: status[key].text, + value: key, + })); + }, [status]); + + const handleSelectStatus = (value) => { + setParams({ taskStatus: value }); + }; + return ( + <> +
工单状态
+ + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + options={taskTypeOptions} + style={{ width: '100%' }} + value={taskTypes} + mode="multiple" + allowClear + onChange={handleSelectType} + /> + + ); +}; +export default TaskTypeFilter; diff --git a/src/component/Task/layout/Header/Search.tsx b/src/component/Task/layout/Header/Search.tsx new file mode 100644 index 000000000..4519c2ab8 --- /dev/null +++ b/src/component/Task/layout/Header/Search.tsx @@ -0,0 +1,54 @@ +import { useContext } from 'react'; +import { formatMessage } from '@/util/intl'; +import ParamsContext from '@/component/Task/context/ParamsContext'; +import { TaskSearchType } from '@/component/Task/interface'; +import InputSelect from '@/component/InputSelect'; + +export const TaskSearchTypeText = { + [TaskSearchType.DESCRIPTION]: '工单描述', + [TaskSearchType.ID]: '工单ID', + [TaskSearchType.CREATOR]: '创建人', + [TaskSearchType.DATABASE]: formatMessage({ + id: 'src.component.ODCSetting.config.9EC92943', + defaultMessage: '数据库', + }), //'数据库' + [TaskSearchType.DATASOURCE]: formatMessage({ + id: 'odc.component.RecordPopover.column.DataSource', + defaultMessage: '数据源', + }), //数据源 + [TaskSearchType.CLUSTER]: formatMessage({ + id: 'odc.Connecion.ConnectionList.ParamContext.Cluster', + defaultMessage: '集群', + }), //集群 + [TaskSearchType.TENANT]: formatMessage({ + id: 'odc.Connecion.ConnectionList.ParamContext.Tenant', + defaultMessage: '租户', + }), //租户 +}; + +const Search = () => { + const context = useContext(ParamsContext); + const { params, setParams } = context; + const { searchValue, searchType } = params; + + const selectTypeOptions = Object.values(TaskSearchType).map((item) => ({ + value: item, + label: TaskSearchTypeText[item], + })); + + return ( + { + setParams({ + searchValue, + searchType: searchType as TaskSearchType, + }); + }} + /> + ); +}; + +export default Search; diff --git a/src/component/Task/layout/Header/Sort.tsx b/src/component/Task/layout/Header/Sort.tsx new file mode 100644 index 000000000..be7b75fee --- /dev/null +++ b/src/component/Task/layout/Header/Sort.tsx @@ -0,0 +1,49 @@ +import { formatMessage } from '@/util/intl'; +import React, { useContext, useState } from 'react'; +import { Dropdown } from 'antd'; +import { inject, observer } from 'mobx-react'; +import { UserStore } from '@/store/login'; +import { ReactComponent as GroupSvg } from '@/svgr/group.svg'; +import Icon from '@ant-design/icons'; +import type { MenuProps } from 'antd'; +import { DatabaseGroup } from '@/d.ts/database'; +import { SortAscendingOutlined } from '@ant-design/icons'; +import FilterIcon from '@/component/Button/FIlterIcon'; +import ParamsContext from '@/component/Task/context/ParamsContext'; + +interface IProps {} + +const items: MenuProps['items'] = [ + { + key: 'createTime,asc', + label: '按创建时间排序', + }, + { + key: 'createTime,desc', + label: '按创建时间降序', + }, +]; + +const Sorter: React.FC = function (props) { + const context = useContext(ParamsContext); + const { params, setParams } = context; + const { sort } = params || {}; + const handleChangeSort = (e) => { + setParams({ sort: e.key }); + }; + + return ( + + + + + + ); +}; +export default inject('userStore')(observer(Sorter)); diff --git a/src/component/Task/layout/Header/Tabs.tsx b/src/component/Task/layout/Header/Tabs.tsx new file mode 100644 index 000000000..e8b9e9ed4 --- /dev/null +++ b/src/component/Task/layout/Header/Tabs.tsx @@ -0,0 +1,38 @@ +import React, { useContext } from 'react'; +import { Radio } from 'antd'; +import { TaskTab } from '@/component/Task/interface'; +import ParamsContext from '@/component/Task/context/ParamsContext'; + +const Tabs = () => { + const context = useContext(ParamsContext); + const { params, setParams } = context; + + const handleSelect = (e) => { + setParams({ tab: e.target.value as TaskTab }); + }; + + return ( + + ); +}; + +export default Tabs; diff --git a/src/component/Task/layout/Header/index.less b/src/component/Task/layout/Header/index.less new file mode 100644 index 000000000..bf50f5cc5 --- /dev/null +++ b/src/component/Task/layout/Header/index.less @@ -0,0 +1,10 @@ +.timeSelect { + :global { + .ant-select-selector { + padding: 0px 4px !important; + } + .ant-select-arrow { + margin-left: 8px; + } + } +} diff --git a/src/component/Task/layout/Header/index.tsx b/src/component/Task/layout/Header/index.tsx new file mode 100644 index 000000000..47d90abff --- /dev/null +++ b/src/component/Task/layout/Header/index.tsx @@ -0,0 +1,26 @@ +import { Space } from 'antd'; +import { ReloadOutlined } from '@ant-design/icons'; +import Search from './Search'; +import Filter from './Filter'; +import Sort from './Sort'; +import Tabs from './Tabs'; +import FilterIcon from '@/component/Button/FIlterIcon'; +import ParamsContext from '@/component/Task/context/ParamsContext'; +import { useCallback, useContext } from 'react'; + +const Header = () => { + const context = useContext(ParamsContext); + + return ( + + + + + + + context?.reload?.()} /> + + + ); +}; +export default Header; diff --git a/src/component/Task/layout/Sider.tsx b/src/component/Task/layout/Sider.tsx new file mode 100644 index 000000000..820e0b3af --- /dev/null +++ b/src/component/Task/layout/Sider.tsx @@ -0,0 +1,97 @@ +import type { PageStore } from '@/store/page'; +import { TaskStore } from '@/store/task'; +import { inject, observer } from 'mobx-react'; +import { useTaskGroup } from '../hooks'; +import { allTaskPageConfig, TaskConfig } from '@/common/task'; +import { Space, Tooltip, Typography } from 'antd'; +import classNames from 'classnames'; +import styles from '@/component/Task/index.less'; +import { useEffect } from 'react'; +import { openTasksPage } from '@/store/helper/page'; +import { TaskPageType } from '@/d.ts'; +import useUrlAction from '@/util/hooks/useUrlAction'; +import useURLParams from '@/util/hooks/useUrlParams'; +import { getFirstEnabledTask } from '@/component/Task/helper'; +import { TaskPageMode } from '../interface'; + +const { Text } = Typography; + +interface IProps { + taskStore?: TaskStore; + pageStore?: PageStore; + className?: string; + mode: TaskPageMode; +} + +const Sider: React.FC = (props) => { + const { taskStore, pageStore, className, mode } = props; + const { results } = useTaskGroup({ + taskItems: [allTaskPageConfig, ...Object.values(TaskConfig)], + }); + const pageKey = + mode === TaskPageMode.MULTI_PAGE ? pageStore?.activePageKey : taskStore?.taskPageType; + const { runTask } = useUrlAction(); + const { getParam, deleteParam } = useURLParams(); + const urlTriggerValue = getParam('filtered'); + const urlStatusValue = getParam('status'); + const firstEnabledTask = getFirstEnabledTask(); + + useEffect(() => { + const res = runTask({ + callback: (task: TaskPageType) => { + openTasksPage(task as TaskPageType); + taskStore.changeTaskPageType(task as TaskPageType); + }, + }); + + if (!res) taskStore.changeTaskPageType(firstEnabledTask?.pageType); + return () => { + taskStore.changeTaskPageType(firstEnabledTask?.pageType); + }; + }, []); + + const handleClick = (value: TaskPageType) => { + urlTriggerValue && deleteParam('filtered'); + urlStatusValue && deleteParam('status'); + if (mode === TaskPageMode.MULTI_PAGE) { + openTasksPage(value); + } + taskStore.changeTaskPageType(value); + }; + + return ( +
+ {results.map((groupItem) => { + const { label, key, children } = groupItem; + return ( +
+ {label && ( + + {label} + + )} + {children.map((item) => { + return ( +
handleClick(item.value)} + > + + {item.label} + +
+ ); + })} +
+ ); + })} +
+ ); +}; +export default inject('taskStore', 'pageStore')(observer(Sider)); diff --git a/src/component/Task/modals/AlterDdlTask/CreateModal/index.tsx b/src/component/Task/modals/AlterDdlTask/CreateModal/index.tsx index 2ed3f9367..92f5069b1 100644 --- a/src/component/Task/modals/AlterDdlTask/CreateModal/index.tsx +++ b/src/component/Task/modals/AlterDdlTask/CreateModal/index.tsx @@ -32,7 +32,6 @@ import { IDatasourceUser, TaskDetail, TaskExecStrategy, - TaskPageScope, TaskPageType, TaskType, } from '@/d.ts'; @@ -229,7 +228,7 @@ const CreateDDLTaskModal: React.FC = (props) => { setConfirmLoading(false); if (res) { handleCancel(false); - openTasksPage(TaskPageType.ONLINE_SCHEMA_CHANGE, TaskPageScope.CREATED_BY_CURRENT_USER); + openTasksPage(TaskPageType.ONLINE_SCHEMA_CHANGE); } }) .catch((errorInfo) => { diff --git a/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx b/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx index 796dbc465..5e19d41c6 100644 --- a/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx +++ b/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx @@ -15,7 +15,7 @@ */ import { getDataSourceModeConfigByConnectionMode } from '@/common/datasource'; import { updateThrottleConfig } from '@/common/network/task'; -import RiskLevelLabel from '@/component/RiskLevelLabel'; +import RiskLevelLabel, { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; import { SQLContent } from '@/component/SQLContent'; import type { ITaskResult, TaskDetail } from '@/d.ts'; import { TaskExecStrategy, TaskStatus } from '@/d.ts'; @@ -31,6 +31,7 @@ import { ClearStrategy, LockStrategy, LockStrategyLableMap } from '../CreateModa import { SwapTableType } from '../CreateModal/const'; import { ProjectRole } from '@/d.ts/project'; import userStore from '@/store/login'; +import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; import EllipsisText from '@/component/EllipsisText'; const { Text } = Typography; @@ -152,8 +153,7 @@ export function getItems( id: 'odc.AlterDdlTask.DetailContent.RiskLevel', defaultMessage: '风险等级', }), - //风险等级 - , + , ]; const timerExecutionItem: [string, string] = [ @@ -203,43 +203,25 @@ export function getItems( { // @ts-ignore textItems: [ - [ - formatMessage({ - id: 'odc.AlterDdlTask.DetailContent.TaskNumber', - defaultMessage: '任务编号', - }), - //任务编号 - task.id, - ], + ['ID', task.id], [ - formatMessage({ - id: 'odc.AlterDdlTask.DetailContent.TaskType', - defaultMessage: '任务类型', - }), - //任务类型 + '类型', formatMessage({ id: 'odc.AlterDdlTask.DetailContent.LockFreeStructureChange', defaultMessage: '无锁结构变更', }), //无锁结构变更 ], - [ - formatMessage({ - id: 'odc.AlterDdlTask.DetailContent.Library', - defaultMessage: '所属库', - }), - //所属库 - , - ], + ['数据库', } />], - [ - formatMessage({ - id: 'odc.src.component.Task.AlterDdlTask.DetailContent.DataSource', - defaultMessage: '所属数据源', - }), - //'所属数据源' - , - ], + ['数据源', ], + ['项目', ], + hasFlow ? riskItem : null, + ].filter(Boolean), + }, + { + // @ts-ignore + textItems: [ [ formatMessage({ id: 'src.component.Task.AlterDdlTask.CreateModal.05BCC428', @@ -247,7 +229,6 @@ export function getItems( }), LockStrategyLableMap[parameters?.forbiddenWriteType || LockStrategy.LOCK_USER], ], - hasFlow ? riskItem : null, lockUsers ? [ formatMessage({ @@ -294,7 +275,6 @@ export function getItems( //完成后源表清理策略 ClearStrategyMap[parameters?.originTableCleanStrategy], ], - [ formatMessage({ id: 'odc.AlterDdlTask.DetailContent.ExecutionMethod', @@ -361,7 +341,6 @@ export function getItems( }), //创建人 task?.creator?.name || '-', - 2, ], [ @@ -371,7 +350,6 @@ export function getItems( }), //创建时间 getFormatDateTime(task.createTime), - 2, ], ], }, diff --git a/src/component/Task/modals/ApplyDatabasePermission/CreateModal/index.tsx b/src/component/Task/modals/ApplyDatabasePermission/CreateModal/index.tsx index 64c9cce1f..f46dc3be5 100644 --- a/src/component/Task/modals/ApplyDatabasePermission/CreateModal/index.tsx +++ b/src/component/Task/modals/ApplyDatabasePermission/CreateModal/index.tsx @@ -18,7 +18,7 @@ import { formatMessage } from '@/util/intl'; import { listProjects } from '@/common/network/project'; import { createTask } from '@/common/network/task'; import DatabaseSelecter from '@/component/Task/component/DatabaseSelecter'; -import { TaskPageScope, TaskPageType, TaskType } from '@/d.ts'; +import { TaskPageType, TaskType } from '@/d.ts'; import { openTasksPage } from '@/store/helper/page'; import type { ModalStore } from '@/store/modal'; import { useRequest } from 'ahooks'; @@ -144,10 +144,7 @@ const CreateModal: React.FC = (props) => { defaultMessage: '工单创建成功', }), ); - openTasksPage( - TaskPageType.APPLY_DATABASE_PERMISSION, - TaskPageScope.CREATED_BY_CURRENT_USER, - ); + openTasksPage(TaskPageType.APPLY_DATABASE_PERMISSION); } }) .catch((errorInfo) => { diff --git a/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx b/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx index bde697ad4..12f0097ed 100644 --- a/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx +++ b/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx @@ -30,6 +30,7 @@ import styles from './index.less'; import { DBType, IDatabase } from '@/d.ts/database'; import DatabaseIcon from '@/component/StatusIcon/DatabaseIcon'; import RiskLevelLabel from '@/component/RiskLevelLabel'; +import { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; import EllipsisText from '@/component/EllipsisText'; const getConnectionColumns = () => { @@ -84,25 +85,9 @@ const TaskContent: React.FC = (props) => { return ( <> - - - {task?.id} - - + + {task?.id} + { formatMessage({ id: 'src.component.Task.ApplyDatabasePermission.DetailContent.176A9CCE' /*申请库权限*/, @@ -117,7 +102,7 @@ const TaskContent: React.FC = (props) => { defaultMessage: '风险等级', })} /*风险等级*/ > - + )} @@ -138,7 +123,7 @@ const TaskContent: React.FC = (props) => { style={{ margin: '4px 0px 8px 0px' }} /> )} - + = (props) => { direction="column" /> - - - + = (props) => { }} /> - + = (props) => { defaultMessage: '工单创建成功', }), ); - openTasksPage( - TaskPageType.APPLY_PROJECT_PERMISSION, - TaskPageScope.CREATED_BY_CURRENT_USER, - ); + openTasksPage(TaskPageType.APPLY_PROJECT_PERMISSION); } }) .catch((errorInfo) => { diff --git a/src/component/Task/modals/ApplyPermission/DetailContent/index.tsx b/src/component/Task/modals/ApplyPermission/DetailContent/index.tsx index c3b1b5b80..1b38a975b 100644 --- a/src/component/Task/modals/ApplyPermission/DetailContent/index.tsx +++ b/src/component/Task/modals/ApplyPermission/DetailContent/index.tsx @@ -33,24 +33,10 @@ const ApplyPermissionTaskContent: React.FC = (props) => { return ( <> - + {task?.id} - + { formatMessage({ id: 'odc.src.component.Task.ApplyPermission.DetailContent.ApplicationProjectPermissions', diff --git a/src/component/Task/modals/ApplyTablePermission/CreateModal/index.tsx b/src/component/Task/modals/ApplyTablePermission/CreateModal/index.tsx index 6579fc99d..13ccbf447 100644 --- a/src/component/Task/modals/ApplyTablePermission/CreateModal/index.tsx +++ b/src/component/Task/modals/ApplyTablePermission/CreateModal/index.tsx @@ -24,7 +24,7 @@ import { flatTableByGroupedParams, groupTableByDataBase, } from '@/component/Task/component/TableSelecter/util'; -import { TaskPageScope, TaskPageType, TaskType } from '@/d.ts'; +import { TaskPageType, TaskType } from '@/d.ts'; import { TablePermissionType } from '@/d.ts/table'; import { openTasksPage } from '@/store/helper/page'; import type { ModalStore } from '@/store/modal'; @@ -249,7 +249,7 @@ const CreateModal: React.FC = (props) => { defaultMessage: '工单创建成功', }), ); - openTasksPage(TaskPageType.APPLY_TABLE_PERMISSION, TaskPageScope.CREATED_BY_CURRENT_USER); + openTasksPage(TaskPageType.APPLY_TABLE_PERMISSION); } }) .catch((errorInfo) => { @@ -331,7 +331,7 @@ const CreateModal: React.FC = (props) => { const applyProjectPermission = () => { handleCancel(hasEdit); - openTasksPage(TaskPageType.APPLY_PROJECT_PERMISSION, TaskPageScope.CREATED_BY_CURRENT_USER); + openTasksPage(TaskPageType.APPLY_PROJECT_PERMISSION); }; const projectEmptyRender = () => { diff --git a/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx b/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx index 60b7b9d30..d07058908 100644 --- a/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx +++ b/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx @@ -22,9 +22,9 @@ import type { IApplyTablePermissionTaskParams, TaskDetail } from '@/d.ts'; import { getFormatDateTime } from '@/util/utils'; import { Descriptions, Divider, Space } from 'antd'; import { useMemo } from 'react'; +import { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; import { permissionOptionsMap } from '../'; import { getExpireTimeLabel } from '@/component/Task/helper'; -import RiskLevelLabel from '@/component/RiskLevelLabel'; import EllipsisText from '@/component/EllipsisText'; const getConnectionColumns = () => { @@ -96,15 +96,8 @@ const TaskContent: React.FC = (props) => { return ( <> - - - {task?.id} - + + {task?.id} = (props) => { defaultMessage: '风险等级', })} /*风险等级*/ > - + )} @@ -133,7 +126,7 @@ const TaskContent: React.FC = (props) => { }} /> - + = (props) => { }} /> - + = (props) => { }} /> - + = (props) => { handleCancel(false); setConfirmLoading(false); if (res) { - openTasksPage(TaskPageType.ASYNC, TaskPageScope.CREATED_BY_CURRENT_USER); + openTasksPage(TaskPageType.ASYNC); } }) .catch((errorInfo) => { diff --git a/src/component/Task/modals/AsyncTask/DetailContent/index.tsx b/src/component/Task/modals/AsyncTask/DetailContent/index.tsx index 3ed28d254..5cc797d62 100644 --- a/src/component/Task/modals/AsyncTask/DetailContent/index.tsx +++ b/src/component/Task/modals/AsyncTask/DetailContent/index.tsx @@ -29,6 +29,7 @@ import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; import styles from './index.less'; import login from '@/store/login'; import { getTaskExecStrategyMap } from '@/component/Task/const'; +import { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; import EllipsisText from '@/component/EllipsisText'; export const ErrorStrategy = { @@ -61,60 +62,26 @@ const AsyncTaskContent: React.FC = (props) => { return ( <> - + {task?.id} - + + 数据库变更 + + - + + - - { - formatMessage({ - id: 'odc.src.component.Task.AsyncTask.DetailContent.DatabaseChange', - defaultMessage: '数据库变更', - }) /* - 数据库变更 - */ - } + + {task?.project?.name || '-'} + {hasFlow && ( = (props) => { }) /* 风险等级 */ } > - - - )} - {!login.isPrivateSpace() && ( - - {node?.status === TaskNodeStatus.EXECUTING ? ( - - ) : task?.affectedRows === -1 ? ( - formatMessage({ - id: 'src.component.Task.AsyncTask.DetailContent.4F8FADD3', - defaultMessage: '该 SQL 语句不支持', - }) - ) : ( - task?.affectedRows || '-' - )} + )} +
= (props) => { marginTop: '8px', }} > + {!login.isPrivateSpace() && ( + + {node?.status === TaskNodeStatus.EXECUTING ? ( + + ) : task?.affectedRows === -1 ? ( + formatMessage({ + id: 'src.component.Task.AsyncTask.DetailContent.4F8FADD3', + defaultMessage: '该 SQL 语句不支持', + }) + ) : ( + task?.affectedRows || '-' + )} + + )} = (props) => { {parameters?.delimiter} = (props) => { {parameters?.queryLimit} = (props) => { {ErrorStrategy[parameters?.errorStrategy]} = (props) => { {parameters?.retryTimes ?? 0} = (props) => { = (props) => { {task?.creator?.name || '-'} = (props) => { - - - - diff --git a/src/component/Task/modals/DataArchiveTask/DetailContent/index.tsx b/src/component/Task/modals/DataArchiveTask/DetailContent/index.tsx deleted file mode 100644 index f38403a71..000000000 --- a/src/component/Task/modals/DataArchiveTask/DetailContent/index.tsx +++ /dev/null @@ -1,447 +0,0 @@ -/* - * 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 { updateLimiterConfig } from '@/common/network/task'; -import RiskLevelLabel from '@/component/RiskLevelLabel'; -import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; -import VariableConfigTable from '@/component/Task/component/VariableConfigTable'; -import { isCycleTriggerStrategy } from '@/component/Task/helper'; -import type { CycleTaskDetail, IDataArchiveJobParameters, TaskOperationType } from '@/d.ts'; -import { TaskExecStrategy } from '@/d.ts'; -import setting from '@/store/setting'; -import { formatMessage } from '@/util/intl'; -import { - getFormatDateTime, - getLocalFormatDateTime, - kbToMb, - mbToKb, - milliSecondsToHour, -} from '@/util/utils'; -import { DownOutlined, UpOutlined } from '@ant-design/icons'; -import { Collapse, Descriptions, Divider, message, Space, Typography } from 'antd'; -import React from 'react'; -import ThrottleEditableCell from '@/component/Task/component/ThrottleEditableCell'; -import { getTaskExecStrategyMap, SyncTableStructureConfig } from '@/component/Task/const'; -import styles from '@/component/Task/index.less'; -import { InsertActionOptions } from '../CreateModal'; -import ArchiveRange from './ArchiveRange'; -import { shardingStrategyOptions } from '@/component/Task/component/ShardingStrategyItem'; -import { isConnectTypeBeFileSystemGroup } from '@/util/connection'; -import { - DirtyRowActionEnum, - DirtyRowActionLabelMap, -} from '@/component/ExecuteSqlDetailModal/constant'; - -const { Text } = Typography; -const { Panel } = Collapse; -interface IProps { - task: CycleTaskDetail; - hasFlow: boolean; - operationType?: TaskOperationType; - onReload?: () => void; -} -const DataArchiveTaskContent: React.FC = (props) => { - const { task, hasFlow, onReload } = props; - const { triggerConfig, jobParameters, id } = task ?? {}; - const taskExecStrategyMap = getTaskExecStrategyMap(task?.type); - const isCycleStrategy = isCycleTriggerStrategy(triggerConfig?.triggerStrategy); - const insertActionLabel = InsertActionOptions?.find( - (item) => item.value === jobParameters?.migrationInsertAction, - )?.label; - const handleRowLimit = async (rowLimit, handleClose) => { - const res = await updateLimiterConfig(id, { - rowLimit, - }); - if (res) { - message.success( - formatMessage({ - id: 'odc.src.component.Task.DataArchiveTask.DetailContent.SuccessfullyModified', - defaultMessage: '修改成功!', - }), //'修改成功!' - ); - handleClose(); - onReload(); - } - }; - const handleDataSizeLimit = async (dataSizeLimit, handleClose) => { - const res = await updateLimiterConfig(id, { - dataSizeLimit: mbToKb(dataSizeLimit), - }); - if (res) { - message.success( - formatMessage({ - id: 'odc.src.component.Task.DataArchiveTask.DetailContent.SuccessfullyModified.1', - defaultMessage: '修改成功!', - }), //'修改成功!' - ); - handleClose(); - onReload(); - } - }; - return ( - <> - - - {task?.id} - - - { - formatMessage({ - id: 'odc.DataArchiveTask.DetailContent.DataArchiving', - defaultMessage: '数据归档', - }) /*数据归档*/ - } - - - - {jobParameters?.sourceDatabase?.name} - {jobParameters?.sourceDatabase?.dataSource?.name} - - - - - {jobParameters?.targetDatabase?.name} - {jobParameters?.targetDatabase?.dataSource?.name} - - - {hasFlow && ( - - - - )} - - - - -
- } - direction="column" - /> - - - -
- } - direction="column" - /> - - - - { - jobParameters?.deleteAfterMigration - ? formatMessage({ - id: 'odc.DataArchiveTask.DetailContent.Yes', - defaultMessage: '是', - }) //是 - : formatMessage({ - id: 'odc.DataArchiveTask.DetailContent.No', - defaultMessage: '否', - }) //否 - } - - {isConnectTypeBeFileSystemGroup(jobParameters?.targetDatabase?.connectType) && ( - - { - jobParameters?.deleteTemporaryTable - ? formatMessage({ - id: 'odc.DataArchiveTask.DetailContent.Yes', - defaultMessage: '是', - }) //是 - : formatMessage({ - id: 'odc.DataArchiveTask.DetailContent.No', - defaultMessage: '否', - }) //否 - } - - )} - {jobParameters?.deleteAfterMigration ? ( - - {DirtyRowActionLabelMap[jobParameters?.dirtyRowAction]} - - ) : null} - {jobParameters?.dirtyRowAction === DirtyRowActionEnum.SKIP ? ( - - {formatMessage( - { - id: 'src.component.Task.DataArchiveTask.DetailContent.A96E9271', - defaultMessage: '{LogicalExpression0} 行', - }, - { LogicalExpression0: jobParameters?.maxAllowedDirtyRowCount || 0 }, - )} - - ) : null} - - - - {taskExecStrategyMap[triggerConfig.triggerStrategy]} - - {triggerConfig.triggerStrategy === TaskExecStrategy.START_AT && ( - - {getLocalFormatDateTime(triggerConfig?.startAt)} - - )} - - {isCycleStrategy && ( - - ( - - {getFormatDateTime(task.nextFireTimes?.[0])} - {isActive ? : } - - } - /> - )} - > - - - {task?.nextFireTimes?.map((item, index) => { - return index > 0 &&
{getFormatDateTime(item)}
; - })} -
-
-
-
- )} - - - {insertActionLabel || '-'} - - - {shardingStrategyOptions.find((item) => item.value === jobParameters?.shardingStrategy) - ?.label || '-'} - - - {jobParameters.timeoutMillis - ? milliSecondsToHour(jobParameters.timeoutMillis) + 'h' - : '-'} - - - {jobParameters?.syncTableStructure?.length - ? formatMessage({ - id: 'src.component.Task.DataArchiveTask.DetailContent.FFC5907D', - defaultMessage: '是', - }) - : formatMessage({ - id: 'src.component.Task.DataArchiveTask.DetailContent.855EA40A', - defaultMessage: '否', - })} - - - {jobParameters?.syncTableStructure && jobParameters?.syncTableStructure?.length - ? jobParameters.syncTableStructure - ?.map((i) => { - return SyncTableStructureConfig[i].label; - }) - .join(',') - : '-'} - - - - - - - - - {task?.description || '-'} - -
- - - - - - {task?.creator?.name || '-'} - - - {getFormatDateTime(task.createTime)} - - - - ); -}; -export default DataArchiveTaskContent; diff --git a/src/component/Task/modals/DataArchiveTask/index.tsx b/src/component/Task/modals/DataArchiveTask/index.tsx deleted file mode 100644 index 11d00ad11..000000000 --- a/src/component/Task/modals/DataArchiveTask/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -export { default } from './CreateModal'; -export { default as DataArchiveTaskContent } from './DetailContent'; diff --git a/src/component/Task/modals/DataClearTask/DetailContent/index.tsx b/src/component/Task/modals/DataClearTask/DetailContent/index.tsx deleted file mode 100644 index 12cda79d5..000000000 --- a/src/component/Task/modals/DataClearTask/DetailContent/index.tsx +++ /dev/null @@ -1,396 +0,0 @@ -/* - * 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 { updateLimiterConfig } from '@/common/network/task'; -import RiskLevelLabel from '@/component/RiskLevelLabel'; -import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; -import VariableConfigTable from '@/component/Task/component/VariableConfigTable'; -import { isCycleTriggerStrategy } from '@/component/Task/helper'; -import type { CycleTaskDetail, IDataClearJobParameters, TaskOperationType } from '@/d.ts'; -import { TaskExecStrategy } from '@/d.ts'; -import setting from '@/store/setting'; -import { formatMessage } from '@/util/intl'; -import { - getFormatDateTime, - getLocalFormatDateTime, - kbToMb, - mbToKb, - milliSecondsToHour, -} from '@/util/utils'; -import { DownOutlined, UpOutlined } from '@ant-design/icons'; -import { Collapse, Descriptions, Divider, message, Space, Typography } from 'antd'; -import React from 'react'; -import ThrottleEditableCell from '@/component/Task/component/ThrottleEditableCell'; -import styles from '@/component/Task/index.less'; -import ArchiveRange from './ArchiveRange'; -import { shardingStrategyOptions } from '@/component/Task/component/ShardingStrategyItem'; -import { - DirtyRowActionEnum, - DirtyRowActionLabelMap, -} from '@/component/ExecuteSqlDetailModal/constant'; -import { getTaskExecStrategyMap } from '@/component/Task/const'; - -const { Panel } = Collapse; -const { Text } = Typography; -interface IProps { - task: CycleTaskDetail; - hasFlow: boolean; - operationType?: TaskOperationType; - onReload?: () => void; -} -const DataClearTaskContent: React.FC = (props) => { - const { task, hasFlow, onReload } = props; - const { triggerConfig, jobParameters, id } = task ?? {}; - const taskExecStrategyMap = getTaskExecStrategyMap(task?.type); - const handleRowLimit = async (rowLimit, handleClose) => { - const res = await updateLimiterConfig(id, { - rowLimit, - }); - if (res) { - message.success( - formatMessage({ - id: 'odc.src.component.Task.DataClearTask.DetailContent.SuccessfullyModified', - defaultMessage: '修改成功!', - }), //'修改成功!' - ); - handleClose(); - onReload(); - } - }; - const handleDataSizeLimit = async (dataSizeLimit, handleClose) => { - const res = await updateLimiterConfig(id, { - dataSizeLimit: mbToKb(dataSizeLimit), - }); - if (res) { - message.success( - formatMessage({ - id: 'odc.src.component.Task.DataClearTask.DetailContent.SuccessfullyModified.1', - defaultMessage: '修改成功!', - }), //'修改成功!' - ); - handleClose(); - onReload(); - } - }; - return ( - <> - - - {task?.id} - - - { - formatMessage({ - id: 'odc.DataClearTask.DetailContent.DataCleansing', - defaultMessage: '数据清理', - }) /*数据清理*/ - } - - {jobParameters?.needCheckBeforeDelete ? ( - - - {jobParameters?.database?.name} - {jobParameters?.database?.dataSource?.name} - - - ) : ( - - - {jobParameters?.database?.name} - {jobParameters?.database?.dataSource?.name} - - - )} - - {jobParameters.needCheckBeforeDelete && ( - - - {jobParameters?.targetDatabase?.name} - {jobParameters?.targetDatabase?.dataSource?.name} - - - )} - - {hasFlow && ( - - - - )} - - - - -
- } - direction="column" - /> - - - -
- } - direction="column" - /> - - - - {taskExecStrategyMap[triggerConfig.triggerStrategy]} - - {triggerConfig.triggerStrategy === TaskExecStrategy.START_AT && ( - - {getLocalFormatDateTime(triggerConfig?.startAt)} - - )} - - {isCycleTriggerStrategy(triggerConfig?.triggerStrategy) && ( - - ( - - {getFormatDateTime(task.nextFireTimes?.[0])} - {isActive ? : } - - } - /> - )} - > - - - {task?.nextFireTimes?.map((item, index) => { - return index > 0 &&
{getFormatDateTime(item)}
; - })} -
-
-
-
- )} - {jobParameters?.needCheckBeforeDelete ? ( - - {DirtyRowActionLabelMap[jobParameters?.dirtyRowAction]} - - ) : null} - {jobParameters?.dirtyRowAction === DirtyRowActionEnum.SKIP ? ( - - {formatMessage( - { - id: 'src.component.Task.DataClearTask.DetailContent.66E3D51C', - defaultMessage: '{LogicalExpression0} 行', - }, - { LogicalExpression0: jobParameters?.maxAllowedDirtyRowCount || 0 }, - )} - - ) : null} - - {shardingStrategyOptions.find((item) => item.value === jobParameters?.shardingStrategy) - ?.label || '-'} - - - - - - - - - {jobParameters?.deleteByUniqueKey - ? formatMessage({ - id: 'src.component.Task.DataClearTask.DetailContent.D2882643', - defaultMessage: '是', - }) - : formatMessage({ - id: 'src.component.Task.DataClearTask.DetailContent.834E7D89', - defaultMessage: '否', - })} - - - {jobParameters.timeoutMillis - ? milliSecondsToHour(jobParameters.timeoutMillis) + 'h' - : '-'} - - - {task?.description || '-'} - -
- - - - - {task?.creator?.name || '-'} - - - {getFormatDateTime(task.createTime)} - - - - ); -}; -export default DataClearTaskContent; diff --git a/src/component/Task/modals/DataClearTask/index.tsx b/src/component/Task/modals/DataClearTask/index.tsx deleted file mode 100644 index f4c27e1da..000000000 --- a/src/component/Task/modals/DataClearTask/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -export { default } from './CreateModal'; -export { default as DataClearTaskContent } from './DetailContent'; diff --git a/src/component/Task/modals/DataMockerTask/CreateModal/form.tsx b/src/component/Task/modals/DataMockerTask/CreateModal/form.tsx index b9e683c80..34ad60166 100644 --- a/src/component/Task/modals/DataMockerTask/CreateModal/form.tsx +++ b/src/component/Task/modals/DataMockerTask/CreateModal/form.tsx @@ -28,7 +28,14 @@ import { Col, Divider, Form, InputNumber, Radio, Row, Select } from 'antd'; import { FormInstance } from 'antd/es/form/Form'; import { cloneDeep } from 'lodash'; import { inject, observer } from 'mobx-react'; -import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react'; +import React, { + forwardRef, + useCallback, + useContext, + useEffect, + useImperativeHandle, + useState, +} from 'react'; import DatabaseSelect from '@/component/Task/component/DatabaseSelect'; import RuleConfigTable from './RuleConfigTable'; import { convertFormToServerColumns, getDefaultRule, getDefaultValue } from './RuleContent'; @@ -201,7 +208,9 @@ const DataMockerForm: React.FC = inject('settingStore')(
form.resetFields(['tableName', 'columns'])} + onChange={(v, db) => { + form.resetFields(['tableName', 'columns']); + }} projectId={projectId} width={'441px'} type={TaskType.DATAMOCK} diff --git a/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx b/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx index fd4d273f3..6396fbc55 100644 --- a/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx +++ b/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx @@ -15,14 +15,7 @@ */ import { createTask } from '@/common/network/task'; -import { - ConnectionMode, - IServerMockTable, - TaskExecStrategy, - TaskPageScope, - TaskPageType, - TaskType, -} from '@/d.ts'; +import { ConnectionMode, IServerMockTable, TaskExecStrategy, TaskPageType, TaskType } from '@/d.ts'; import { openTasksPage } from '@/store/helper/page'; import { ModalStore } from '@/store/modal'; import { formatMessage } from '@/util/intl'; @@ -30,7 +23,7 @@ import { Button, Drawer, message, Modal, Space } from 'antd'; import { DrawerProps } from 'antd/es/drawer'; import { FormInstance } from 'antd/es/form/Form'; import { inject, observer } from 'mobx-react'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import DataMockerForm, { converFormToServerData } from './form'; import { IMockFormData } from './type'; import dayjs from 'dayjs'; @@ -50,6 +43,7 @@ const CreateModal: React.FC = inject('modalStore')( const [dbMode, setDbMode] = useState(null); const formRef = useRef>(null); const [ruleConfigList, setRuleConfigList] = useState([]); + const [open, setOpen] = useState(false); const loadEditData = async () => { const { task } = dataMockerData; @@ -124,6 +118,61 @@ const CreateModal: React.FC = inject('modalStore')( setDbMode(mode); }; + const handleCreate = async () => { + try { + const values = await formRef.current.validateFields(); + + const editingColumn = values?.columns?.find((c) => { + return c.typeConfig?._isEditing; + }); + if (editingColumn) { + message.warning( + formatMessage( + { + id: 'odc.component.DataMockerDrawer.TheFieldEditingcolumncolumnnameIsBeing', + defaultMessage: '字段{editingColumnColumnName}正在编辑中', + }, + + { editingColumnColumnName: editingColumn.columnName }, + ), + // `字段${editingColumn.columnName}正在编辑中` + ); + return; + } else if (!values?.columns?.length) { + return; + } + setConfirmLoading(true); + const { databaseName, databaseId, executionStrategy, executionTime, description, ...rest } = + values; + const serverData = converFormToServerData(rest as any, dbMode, databaseName); + const isSuccess = await createTask({ + projectId, + databaseId, + executionStrategy, + executionTime: executionStrategy === TaskExecStrategy.TIMER ? executionTime : undefined, + taskType: TaskType.DATAMOCK, + parameters: { + taskDetail: JSON.stringify(serverData), + }, + description, + }); + setConfirmLoading(false); + if (isSuccess) { + message.success( + formatMessage({ + id: 'src.component.Task.DataMockerTask.CreateModal.753EA4C0' /*'工单创建成功'*/, + defaultMessage: '工单创建成功', + }), + ); + onClose(); + openTasksPage(TaskPageType.DATAMOCK); + } + } catch (e) { + formRef?.current?.scrollToField(e?.errorFields?.[0]?.name); + console.log(e); + } + }; + useEffect(() => { if (dataMockerData?.task) { loadEditData(); @@ -152,74 +201,7 @@ const CreateModal: React.FC = inject('modalStore')( /* 取消 */ } - - - - - - } - > - - - - {hasPartitionPlan && ( - - )} - - - - - - - - - { - handleCrontabChange(value); - }} - /> - - - - - { - formatMessage({ - id: 'src.component.Task.PartitionTask.CreateModal.BE341FCE' /*自定义删除策略执行周期*/, - defaultMessage: '自定义删除策略执行周期', - }) /* 自定义删除策略执行周期 */ - } - - {!isCustomStrategy && ( - - - { - formatMessage({ - id: 'src.component.Task.PartitionTask.CreateModal.5DEF5FCE' /*未勾选时,删除策略执行周期将与创建一致*/, - defaultMessage: '未勾选时,删除策略执行周期将与创建一致', - }) /* 未勾选时,删除策略执行周期将与创建一致 */ - } - - - )} - - - {isCustomStrategy && ( - - { - handleCrontabChange(value, true); - }} - /> - - )} - - - - - - - - - - - - { - formatMessage({ - id: 'src.component.Task.PartitionTask.CreateModal.53678847' /*小时*/, - defaultMessage: '小时', - }) /* 小时 */ - } - - - - - - - ); - }), -); -export default CreateModal; diff --git a/src/component/Task/modals/PartitionTask/DetailContent/index.tsx b/src/component/Task/modals/PartitionTask/DetailContent/index.tsx deleted file mode 100644 index a4b1b7387..000000000 --- a/src/component/Task/modals/PartitionTask/DetailContent/index.tsx +++ /dev/null @@ -1,203 +0,0 @@ -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 RiskLevelLabel from '@/component/RiskLevelLabel'; -import type { IIPartitionPlanTaskDetail, IPartitionPlanParams, ITaskResult } from '@/d.ts'; -import { getFormatDateTime, milliSecondsToHour } from '@/util/utils'; -import { Descriptions, Divider, Typography } from 'antd'; -import React from 'react'; -import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; -import PartitionPolicyTable from '@/component/Task/component/PartitionPolicyTable'; -import { ErrorStrategyMap } from '@/component/Task/const'; -import CycleDescriptionItem from './CycleDescriptionItem'; -import EllipsisText from '@/component/EllipsisText'; - -const { Text } = Typography; - -interface IProps { - task: IIPartitionPlanTaskDetail; - result: ITaskResult; - hasFlow: boolean; -} - -const PartitionTaskContent: React.FC = (props) => { - const { task, hasFlow } = props; - const { - creationTrigger, - createTriggerNextFireTimes, - droppingTrigger, - dropTriggerNextFireTimes, - errorStrategy, - timeoutMillis, - } = task?.parameters ?? {}; - const executionTimeout = milliSecondsToHour(timeoutMillis); - - return ( - <> - - - {task?.id} - - - { - formatMessage({ - id: 'odc.src.component.Task.PartitionTask.DetailContent.Partition', - defaultMessage: '分区计划', - }) /* - 分区计划 - */ - } - - - - - - - - {hasFlow && ( - - - - )} - - - - {creationTrigger && ( - - )} - - {droppingTrigger && ( - - )} - - - - {ErrorStrategyMap[errorStrategy]} - - - {executionTimeout || '-'} - {formatMessage({ - id: 'src.component.Task.PartitionTask.DetailContent.B08D0E80' /*小时*/, - defaultMessage: '小时', - })} - - - {task?.description || '-'} - - - - - - {task?.creator?.name || '-'} - - - {getFormatDateTime(task.createTime)} - - - - ); -}; -export default PartitionTaskContent; diff --git a/src/component/Task/modals/PartitionTask/index.tsx b/src/component/Task/modals/PartitionTask/index.tsx deleted file mode 100644 index 0b28e4adb..000000000 --- a/src/component/Task/modals/PartitionTask/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -export { default } from './CreateModal'; -export { default as PartitionTaskContent } from './DetailContent'; diff --git a/src/component/Task/modals/PermissionApplication/CreateModal/index.tsx b/src/component/Task/modals/PermissionApplication/CreateModal/index.tsx index 9e07c877a..cf88370bd 100644 --- a/src/component/Task/modals/PermissionApplication/CreateModal/index.tsx +++ b/src/component/Task/modals/PermissionApplication/CreateModal/index.tsx @@ -34,6 +34,7 @@ import { DrawerProps } from 'antd/es/drawer'; import { inject, observer } from 'mobx-react'; import React, { useCallback, useState } from 'react'; import styles from './index.less'; +import { ScheduleType } from '@/d.ts/schedule'; export enum IPartitionPlanInspectTriggerStrategy { EVERY_DAY = 'EVERY_DAY', @@ -88,7 +89,7 @@ const CreateModal: React.FC = inject('modalStore')( // // 4.0.0 禁止设置 巡检周期,保留一个默认值:无需巡检 // const inspectTriggerStrategy = IPartitionPlanInspectTriggerStrategy.NONE; // const params = { - // taskType: TaskType.PARTITION_PLAN, + // taskType: ScheduleType.PARTITION_PLAN, // databaseName: schemaStore.database.name, // description, // connectionId, @@ -106,10 +107,6 @@ const CreateModal: React.FC = inject('modalStore')( // const resCount = await createTask(params); setConfirmLoading(false); onClose(); - // if (resCount) { - // onClose(); - // openTasksPage(TaskPageType.PARTITION_PLAN, TaskPageScope.CREATED_BY_CURRENT_USER); - // } } catch (e) { console.log(e); } diff --git a/src/component/Task/modals/ResultSetExportTask/CreateModal/index.tsx b/src/component/Task/modals/ResultSetExportTask/CreateModal/index.tsx index 146e118c8..a00f261ea 100644 --- a/src/component/Task/modals/ResultSetExportTask/CreateModal/index.tsx +++ b/src/component/Task/modals/ResultSetExportTask/CreateModal/index.tsx @@ -25,7 +25,6 @@ import { IExportResultSetFileType, IMPORT_ENCODING, TaskExecStrategy, - TaskPageScope, TaskPageType, TaskType, } from '@/d.ts'; @@ -142,7 +141,7 @@ const CreateModal: React.FC = (props) => { handleCancel(false); setConfirmLoading(false); if (res) { - openTasksPage(TaskPageType.EXPORT_RESULT_SET, TaskPageScope.CREATED_BY_CURRENT_USER); + openTasksPage(TaskPageType.EXPORT_RESULT_SET); } }) .catch((errorInfo) => { diff --git a/src/component/Task/modals/ResultSetExportTask/DetailContent/index.tsx b/src/component/Task/modals/ResultSetExportTask/DetailContent/index.tsx index 4bb5e3feb..a70a14434 100644 --- a/src/component/Task/modals/ResultSetExportTask/DetailContent/index.tsx +++ b/src/component/Task/modals/ResultSetExportTask/DetailContent/index.tsx @@ -16,12 +16,12 @@ import { formatMessage } from '@/util/intl'; */ import { getDataSourceModeConfigByConnectionMode } from '@/common/datasource'; -import RiskLevelLabel from '@/component/RiskLevelLabel'; +import RiskLevelLabel, { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; import { SQLContent } from '@/component/SQLContent'; import type { IResultSetExportTaskParams, ITaskResult, TaskDetail } from '@/d.ts'; import { IExportResultSetFileType, TaskExecStrategy } from '@/d.ts'; import { CRLFToSeparatorString, getFormatDateTime } from '@/util/utils'; -import { Divider } from 'antd'; +import { Descriptions, Divider } from 'antd'; import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; import { getTaskExecStrategyMap } from '@/component/Task/const'; @@ -67,62 +67,42 @@ export const getItems = ( return ( <> - - - } - /> - - } - /> - - + {task?.id} + + {formatMessage({ id: 'odc.src.component.Task.ResultSetExportTask.DetailContent.ExportResultSet', defaultMessage: '导出结果集', - }) /* 导出结果集 */ - } - /> - - {hasFlow && ( - + + + + + {task?.database?.dataSource?.name || '-'} + + + {task?.project?.name || '-'} + + {hasFlow && ( + } - /> - )} + })} + > + + + )} + + + - - - - - - + {parameters?.maxRows} + {parameters?.fileName} + + })} + > + {parameters?.fileFormat} + - {parameters?.fileFormat === IExportResultSetFileType.CSV && ( - <> - + + })} + > + {csvFormat?.join('、')} + - + })} + > + {parameters?.csvFormat?.columnSeparator} + - + })} + > + {parameters?.csvFormat?.columnDelimiter} + - - - )} - - {parameters?.fileFormat === IExportResultSetFileType.SQL && ( - + {CRLFToSeparatorString(parameters?.csvFormat?.lineSeparator)} + + + )} + {parameters?.fileFormat === IExportResultSetFileType.SQL && ( + - )} - - {parameters?.fileFormat === IExportResultSetFileType.EXCEL && ( - <> - - - - - )} - - - - + {parameters?.tableName ?? '-'} + + )} + {parameters?.fileFormat === IExportResultSetFileType.EXCEL && ( + + {parameters?.csvFormat?.isContainColumnHeader + ? formatMessage({ + id: 'odc.src.component.Task.ResultSetExportTask.DetailContent.Yes', + defaultMessage: '是', + }) //'是' + : formatMessage({ + id: 'odc.src.component.Task.ResultSetExportTask.DetailContent.No', + defaultMessage: '否', + })} + + )} + {parameters?.fileEncoding} + + })} + > + {taskExecStrategyMap[task?.executionStrategy]} + - {task?.executionStrategy === TaskExecStrategy.TIMER && ( - - )} - - + })} + > + {getFormatDateTime(task?.executionTime)} + + )} + + + + {task?.description} + + - - - - + + {task?.creator?.name || '-'} + + {getFormatDateTime(task?.createTime)} + + ); }, diff --git a/src/component/Task/modals/SQLPlanTask/CreateModal/index.tsx b/src/component/Task/modals/SQLPlanTask/CreateModal/index.tsx deleted file mode 100644 index 98db6e37b..000000000 --- a/src/component/Task/modals/SQLPlanTask/CreateModal/index.tsx +++ /dev/null @@ -1,777 +0,0 @@ -/* - * 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 { getDataSourceModeConfig } from '@/common/datasource'; -import { createTask, getAsyncTaskUploadUrl, getCycleTaskDetail } from '@/common/network/task'; -import CommonIDE from '@/component/CommonIDE'; -import Crontab from '@/component/Crontab'; -import { CrontabDateType, CrontabMode, ICrontab } from '@/component/Crontab/interface'; -import FormItemPanel from '@/component/FormItemPanel'; -import ODCDragger from '@/component/OSSDragger2'; -import DescriptionInput from '@/component/Task/component/DescriptionInput'; -import { - CreateTaskRecord, - ISqlPlayJobParameters, - SQLContentType, - TaskExecStrategy, - TaskOperationType, - TaskPageScope, - TaskPageType, - TaskStatus, - TaskType, - CycleTaskDetail, -} from '@/d.ts'; -import { openTasksPage } from '@/store/helper/page'; -import login from '@/store/login'; -import type { ModalStore } from '@/store/modal'; -import { useDBSession } from '@/store/sessionManager/hooks'; -import { formatMessage } from '@/util/intl'; -import { getLocale } from '@umijs/max'; -import { - AutoComplete, - Button, - Drawer, - Form, - InputNumber, - message, - Modal, - Radio, - Space, - Spin, -} from 'antd'; -import type { UploadFile } from 'antd/lib/upload/interface'; -import Cookies from 'js-cookie'; -import { inject, observer } from 'mobx-react'; -import React, { useEffect, useRef, useState } from 'react'; -import DatabaseSelect from '@/component/Task/component/DatabaseSelect'; -import { useRequest } from 'ahooks'; -import styles from './index.less'; -import setting from '@/store/setting'; -import { rules } from './const'; -import { Rule } from '@@node_modules/antd/es/form'; - -const MAX_FILE_SIZE = 1024 * 1024 * 256; - -interface IProps { - modalStore?: ModalStore; - projectId?: number; - theme?: string; -} - -enum ErrorStrategy { - CONTINUE = 'CONTINUE', - ABORT = 'ABORT', -} - -const defaultValue = { - sqlContentType: SQLContentType.TEXT, - delimiter: ';', - timeoutMillis: 48, - errorStrategy: ErrorStrategy.ABORT, - allowConcurrent: false, -}; - -const CreateModal: React.FC = (props) => { - const { modalStore, projectId, theme } = props; - const [sqlContentType, setSqlContentType] = useState(SQLContentType.TEXT); - const [formData, setFormData] = useState(null); - const [hasEdit, setHasEdit] = useState(false); - const [confirmLoading, setConfirmLoading] = useState(false); - const [crontab, setCrontab] = useState(null); - const [form] = Form.useForm(); - const databaseId = Form.useWatch('databaseId', form); - const { database } = useDBSession(databaseId); - const connection = database?.dataSource; - const crontabRef = useRef<{ - setValue: (value: ICrontab) => void; - resetFields: () => void; - }>(); - - const { createSQLPlanVisible, sqlPlanData } = modalStore; - const SQLPlanEditId = sqlPlanData?.id; - const taskId = sqlPlanData?.taskId; - const isEdit = !!SQLPlanEditId || !!taskId; - const isInitContent = isEdit ? isEdit && formData : true; - const { run: fetchCycleTaskDetail, loading } = useRequest(getCycleTaskDetail, { manual: true }); - - const loadEditData = async (editId: number) => { - const data = (await fetchCycleTaskDetail(editId)) as CycleTaskDetail; - - const { - jobParameters, - triggerConfig: { triggerStrategy, cronExpression, hours, days }, - database: { id: databaseId }, - allowConcurrent, - ...rest - } = data; - const sqlContentType = jobParameters?.sqlObjectIds ? SQLContentType.FILE : SQLContentType.TEXT; - const formData = { - ...rest, - ...jobParameters, - databaseId, - sqlContentType, - sqlFiles: undefined, - timeoutMillis: jobParameters.timeoutMillis / 1000 / 60 / 60, - allowConcurrent, - }; - - if (sqlContentType === SQLContentType.FILE) { - const sqlFiles = jobParameters?.sqlObjectIds?.map((id, i) => { - return { - uid: i, - name: jobParameters?.sqlObjectNames[i], - status: 'done', - response: { - data: { - contents: [{ objectId: id }], - }, - }, - }; - }); - formData.sqlFiles = sqlFiles; - } - setSqlContentType(sqlContentType); - setFormData(formData); - form.setFieldsValue(formData); - crontabRef.current?.setValue({ - mode: triggerStrategy === TaskExecStrategy.CRON ? CrontabMode.custom : CrontabMode.default, - dateType: triggerStrategy as any, - cronString: cronExpression, - hour: hours, - dayOfMonth: days, - dayOfWeek: days, - }); - }; - - const loadInitialDataFromSpaceConfig = () => { - form.setFieldValue( - 'queryLimit', - Number(setting.getSpaceConfigByKey('odc.sqlexecute.default.queryLimit')), - ); - }; - - useEffect(() => { - if (!createSQLPlanVisible) return; - - if (SQLPlanEditId || taskId) { - loadEditData(SQLPlanEditId || taskId); - } else { - loadInitialDataFromSpaceConfig(); - } - }, [SQLPlanEditId, taskId, createSQLPlanVisible]); - - const setFormStatus = (fieldName: string, errorMessage: string) => { - form.setFields([ - { - name: [fieldName], - errors: errorMessage ? [errorMessage] : [], - }, - ]); - }; - - const handleCancel = (hasEdit: boolean) => { - if (hasEdit) { - Modal.confirm({ - title: formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.AreYouSureYouWant', - defaultMessage: '是否确认取消此 SQL 计划?', - }), - //确认取消此 SQL 计划吗? - centered: true, - onOk: () => { - props.modalStore.changeCreateSQLPlanTaskModal(false); - }, - }); - } else { - props.modalStore.changeCreateSQLPlanTaskModal(false); - } - }; - - const handleCrontabChange = (crontab) => { - setCrontab(crontab); - }; - - const getFileIdAndNames = (files: UploadFile[]) => { - const ids = []; - const names = []; - files - ?.filter((file) => file?.status === 'done') - ?.forEach((file) => { - ids.push(file?.response?.data?.contents?.[0]?.objectId); - names.push(file?.name); - }); - return { - ids, - names, - size: ids.length, - }; - }; - - const checkFileSizeAmount = (files: UploadFile[]) => { - const fileSizeAmount = files?.reduce((prev, current) => { - return prev + current.size; - }, 0); - if (fileSizeAmount > MAX_FILE_SIZE) { - /** - * 校验文件总大小 - */ - message.warning( - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.UpToMbOfFiles', - defaultMessage: '文件最多不超过 256 MB', - }), - //文件最多不超过 256MB - ); - return false; - } - return true; - }; - - const handleCreate = async (data: Partial) => { - const res = await createTask(data); - handleCancel(false); - setConfirmLoading(false); - if (res) { - openTasksPage(TaskPageType.SQL_PLAN, TaskPageScope.CREATED_BY_CURRENT_USER); - } - }; - - const handleEditAndConfirm = async (data: Partial) => { - Modal.confirm({ - title: formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.AreYouSureYouWant.1', - defaultMessage: '是否确认修改此 SQL 计划?', - }), - //确认要修改此 SQL 计划吗? - content: ( - <> -
- { - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.EditSqlPlan', - defaultMessage: '编辑 SQL 计划', - }) - /*编辑 SQL 计划*/ - } -
-
- { - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.TheTaskNeedsToBe', - defaultMessage: '任务需要重新审批,审批通过后此任务将重新执行', - }) - /*任务需要重新审批,审批通过后此任务将重新执行*/ - } -
- - ), - - cancelText: formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.Cancel', - defaultMessage: '取消', - }), - //取消 - okText: formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.Ok', - defaultMessage: '确定', - }), //确定 - centered: true, - onOk: () => { - handleCreate(data); - }, - onCancel: () => { - setConfirmLoading(false); - }, - }); - }; - - const handleSubmit = () => { - form - .validateFields() - .then(async (values) => { - const { - databaseId, - sqlContentType, - sqlContent, - sqlFiles, - timeoutMillis, - queryLimit, - delimiter, - errorStrategy, - allowConcurrent, - description, - } = values; - const sqlFileIdAndNames = getFileIdAndNames(sqlFiles); - const { mode, dateType, cronString, hour, dayOfMonth, dayOfWeek } = crontab; - const parameters = { - taskId: SQLPlanEditId, - type: TaskType.SQL_PLAN, - operationType: - isEdit && SQLPlanEditId ? TaskOperationType.UPDATE : TaskOperationType.CREATE, - allowConcurrent, - scheduleTaskParameters: { - timeoutMillis: timeoutMillis ? timeoutMillis * 60 * 60 * 1000 : undefined, - errorStrategy, - sqlContent, - sqlObjectIds: sqlFileIdAndNames?.ids, - sqlObjectNames: sqlFileIdAndNames?.names, - queryLimit, - delimiter, - }, - - triggerConfig: { - triggerStrategy: mode === 'custom' ? 'CRON' : dateType, - days: dateType === CrontabDateType.weekly ? dayOfWeek : dayOfMonth, - hours: hour, - cronExpression: cronString, - }, - }; - - if (!checkFileSizeAmount(sqlFiles)) { - return; - } - if (sqlContentType === SQLContentType.FILE) { - delete parameters.scheduleTaskParameters.sqlContent; - if (sqlFiles?.some((item) => item?.error?.isLimit)) { - setFormStatus( - 'sqlFiles', - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.UpToMbOfFiles', - defaultMessage: '文件最多不超过 256 MB', - }), - //文件最多不超过 256MB - ); - return; - } - - if (!sqlFileIdAndNames?.size || sqlFileIdAndNames?.size !== sqlFiles?.length) { - setFormStatus( - 'sqlFiles', - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.PleaseUploadTheSqlFile', - defaultMessage: '请上传 SQL 文件', - }), - //请上传 SQL 文件 - ); - return; - } - } else { - delete parameters.scheduleTaskParameters.sqlObjectIds; - delete parameters.scheduleTaskParameters.sqlObjectNames; - } - - const data = { - projectId, - databaseId, - taskType: TaskType.ALTER_SCHEDULE, - parameters, - description, - }; - - setConfirmLoading(true); - if (!isEdit && SQLPlanEditId) { - delete parameters.taskId; - } - if (isEdit && formData?.status !== TaskStatus.PAUSE && SQLPlanEditId) { - handleEditAndConfirm(data); - } else { - handleCreate(data); - } - }) - .catch((errorInfo) => { - form.scrollToField(errorInfo?.errorFields?.[0]?.name); - console.error(JSON.stringify(errorInfo)); - }); - }; - - const handleContentTypeChange = (e) => { - setSqlContentType(e.target.value); - }; - - const handleSqlChange = (sql: string) => { - form?.setFieldsValue({ - sqlContent: sql, - }); - - setHasEdit(true); - }; - - const handleFieldsChange = () => { - setHasEdit(true); - }; - - const handleBeforeUpload = (file) => { - const isLt20M = MAX_FILE_SIZE > file.size; - if (!isLt20M) { - setTimeout(() => { - setFormStatus( - 'sqlFiles', - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.UpToMbOfFiles', - defaultMessage: '文件最多不超过 256 MB', - }), - //文件最多不超过 256MB - ); - }, 0); - } - return isLt20M; - }; - - const handleFileChange = (files: UploadFile[]) => { - form?.setFieldsValue({ - sqlFiles: files, - }); - - if (files.some((item) => item?.error?.isLimit)) { - setFormStatus( - 'sqlFiles', - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.UpToMbOfFiles', - defaultMessage: '文件最多不超过 256 MB', - }), - //文件最多不超过 256MB - ); - } else { - setFormStatus('sqlFiles', ''); - } - }; - - const draggerProps = { - accept: '.sql', - uploadFileOpenAPIName: 'UploadFile', - onBeforeUpload: handleBeforeUpload, - multiple: true, - tip: formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.YouCanDragAndDrop', - defaultMessage: '支持拖拽文件上传,任务将按文件排列的先后顺序执行', - }), - //支持拖拽文件上传,任务将按文件排列的先后顺序执行 - maxCount: 500, - action: getAsyncTaskUploadUrl(), - headers: { - 'X-XSRF-TOKEN': Cookies.get('XSRF-TOKEN') || '', - 'Accept-Language': getLocale(), - currentOrganizationId: login.organizationId?.toString(), - }, - - defaultFileList: formData?.sqlFiles, - onFileChange: handleFileChange, - }; - - const handleReset = () => { - setFormData(null); - setSqlContentType(SQLContentType.TEXT); - form?.resetFields(); - setCrontab(null); - }; - - useEffect(() => { - if (!createSQLPlanVisible) { - handleReset(); - } - }, [createSQLPlanVisible]); - - useEffect(() => { - const databaseId = sqlPlanData?.databaseId; - if (databaseId) { - form.setFieldsValue({ - databaseId, - }); - } - }, [sqlPlanData?.databaseId]); - - return ( - - - - - } - open={createSQLPlanVisible} - onClose={() => { - handleCancel(hasEdit); - }} - > - -
- - - - - - - {isInitContent && ( - - )} - - - {isInitContent && ( - -

- { - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.ClickOrDragMultipleFiles', - defaultMessage: '点击或将多个文件拖拽到这里上传', - }) - /*点击或将多个文件拖拽到这里上传*/ - } -

-

- { - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.TheFileCanBeUp', - defaultMessage: '文件最多不超过 256 MB ,支持扩展名 .sql', - }) - /*文件最多不超过 256MB ,支持扩展名 .sql*/ - } -

-
- )} -
- - - { - return { - value, - }; - })} - /> - - - - - - - - - - { - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.Hours', - defaultMessage: '小时', - }) - /*小时*/ - } - - - - - - - - - - - - - - - - -
-
- ); -}; - -export default inject('modalStore')(observer(CreateModal)); diff --git a/src/component/Task/modals/SQLPlanTask/CreateModal/interface.ts b/src/component/Task/modals/SQLPlanTask/CreateModal/interface.ts deleted file mode 100644 index 8fa058f1b..000000000 --- a/src/component/Task/modals/SQLPlanTask/CreateModal/interface.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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. - */ - -export enum TriggerStrategy { - DAY = 'DAY', - WEEK = 'WEEK', - MONTH = 'MONTH', - CRON = 'CRON', -} diff --git a/src/component/Task/modals/SQLPlanTask/DetailContent/index.tsx b/src/component/Task/modals/SQLPlanTask/DetailContent/index.tsx deleted file mode 100644 index b5688767b..000000000 --- a/src/component/Task/modals/SQLPlanTask/DetailContent/index.tsx +++ /dev/null @@ -1,301 +0,0 @@ -/* - * 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 { getDataSourceModeConfigByConnectionMode } from '@/common/datasource'; -import RiskLevelLabel from '@/component/RiskLevelLabel'; -import { SQLContent } from '@/component/SQLContent'; -import { operationTypeMap } from '@/component/Task/component/CommonDetailModal/TaskOperationRecord'; -import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; -import type { CycleTaskDetail, ISqlPlayJobParameters, TaskOperationType } from '@/d.ts'; -import { TaskType } from '@/d.ts'; -import { formatMessage } from '@/util/intl'; -import { getFormatDateTime, milliSecondsToHour } from '@/util/utils'; -import { DownOutlined, UpOutlined } from '@ant-design/icons'; -import { Collapse, Descriptions, Divider, Space } from 'antd'; -import React from 'react'; -import { getCronCycle } from '@/component/Task/component/TaskTable/utils'; -import styles from '@/component/Task/index.less'; -import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; -import EllipsisText from '@/component/EllipsisText'; - -const { Panel } = Collapse; -const ErrorStrategy = { - ABORT: formatMessage({ - id: 'odc.component.DetailModal.sqlPlan.StopATask', - defaultMessage: '停止任务', - }), //停止任务 - CONTINUE: formatMessage({ - id: 'odc.component.DetailModal.sqlPlan.IgnoreErrorsToContinueThe', - defaultMessage: '忽略错误继续任务', - }), - //忽略错误继续任务 -}; - -const CycleTaskLabel = { - [TaskType.ALTER_SCHEDULE]: formatMessage({ - id: 'odc.component.DetailModal.sqlPlan.PlannedChange', - defaultMessage: '计划变更', - }), - //计划变更 - [TaskType.SQL_PLAN]: formatMessage({ - id: 'odc.component.DetailModal.sqlPlan.SqlPlan', - defaultMessage: 'SQL 计划', - }), - //SQL 计划 -}; - -interface IProps { - task: CycleTaskDetail; - hasFlow: boolean; - operationType?: TaskOperationType; - theme?: string; -} - -const SqlPlanTaskContent: React.FC = (props) => { - const { task, hasFlow, operationType, theme } = props; - const { jobParameters, triggerConfig, allowConcurrent } = task ?? {}; - const executionTimeout = milliSecondsToHour(jobParameters?.timeoutMillis); - - return ( - <> - - - {task?.id} - - - - - - - - - - {CycleTaskLabel[task?.type]} - - {hasFlow && ( - - - - )} - - {operationType && ( - - {operationTypeMap[operationType]} - - )} - - - - - } - direction="column" - /> - - - - {triggerConfig ? getCronCycle(triggerConfig) : '-'} - - {task?.type === TaskType.SQL_PLAN && ( - - ( - - {getFormatDateTime(task.nextFireTimes?.[0])} - {isActive ? : } - - } - /> - )} - > - - - {task?.nextFireTimes?.map((item, index) => { - return index > 0 &&
{getFormatDateTime(item)}
; - })} -
-
-
-
- )} - - - {jobParameters?.delimiter} - - - {jobParameters?.queryLimit} - - - {ErrorStrategy[jobParameters?.errorStrategy]} - - - { - formatMessage( - { - id: 'odc.component.DetailModal.sqlPlan.ExecutiontimeoutHours', - defaultMessage: '{executionTimeout}小时', - }, - - { executionTimeout }, - ) - //`${executionTimeout}小时` - } - - - { - allowConcurrent - ? formatMessage({ - id: 'odc.component.DetailModal.sqlPlan.IgnoreTheCurrentTaskStatus', - defaultMessage: '忽略当前任务状态,定期发起新任务', - }) //忽略当前任务状态,定期发起新任务 - : formatMessage({ - id: 'odc.component.DetailModal.sqlPlan.AfterTheCurrentTaskIs', - defaultMessage: '待当前任务执行完毕在新周期发起任务', - }) //待当前任务执行完毕在新周期发起任务 - } - - - {task?.description || '-'} - -
- - - - {task?.creator?.name || '-'} - - - {getFormatDateTime(task.createTime)} - - - - ); -}; - -export default SqlPlanTaskContent; diff --git a/src/component/Task/modals/SQLPlanTask/index.tsx b/src/component/Task/modals/SQLPlanTask/index.tsx deleted file mode 100644 index 491c35f6e..000000000 --- a/src/component/Task/modals/SQLPlanTask/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -export { default } from './CreateModal'; -export { default as SqlPlanTaskContent } from './DetailContent'; diff --git a/src/component/Task/modals/ShadowSyncTask/CreateModal/index.tsx b/src/component/Task/modals/ShadowSyncTask/CreateModal/index.tsx index c5f078291..1953cb2f5 100644 --- a/src/component/Task/modals/ShadowSyncTask/CreateModal/index.tsx +++ b/src/component/Task/modals/ShadowSyncTask/CreateModal/index.tsx @@ -25,7 +25,7 @@ import SelectPanel from './SelectPanel'; import StructConfigPanel from './StructConfigPanel'; import { createTask, getTaskDetail } from '@/common/network/task'; -import { TaskExecStrategy, TaskPageScope, TaskPageType, TaskType } from '@/d.ts'; +import { TaskExecStrategy, TaskPageType, TaskType } from '@/d.ts'; import { openTasksPage } from '@/store/helper/page'; import styles from './index.less'; import dayjs from 'dayjs'; @@ -179,7 +179,7 @@ const CreateModal: React.FC = function ({ modalStore, projectId }) { return; } close(true); - openTasksPage(TaskPageType.SHADOW, TaskPageScope.CREATED_BY_CURRENT_USER); + openTasksPage(TaskPageType.SHADOW); } return ( , + , ]; const isTimerExecution = task?.executionStrategy === TaskExecStrategy.TIMER; @@ -125,29 +125,17 @@ export function getItems( { //@ts-ignore textItems: [ - [ - formatMessage({ - id: 'odc.component.DetailModal.permission.TaskNumber', - defaultMessage: '任务编号', - }), - task.id, - ], + ['ID', task.id], [ - formatMessage({ - id: 'odc.component.DetailModal.permission.TaskType', - defaultMessage: '任务类型', - }), + '类型', formatMessage({ id: 'odc.component.DetailModal.shadowSync.ShadowTableSynchronization', defaultMessage: '影子表同步', }), //影子表同步 ], [ - formatMessage({ - id: 'odc.component.DetailModal.dataMocker.Database', - defaultMessage: '所属数据库', - }), + '数据库', //所属数据库 , ], @@ -159,6 +147,7 @@ export function getItems( }), //'所属数据源' , ], + ['项目', ], hasFlow ? riskItem : null, [ diff --git a/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx b/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx index 1d321a6df..fb3998e43 100644 --- a/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx +++ b/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx @@ -548,31 +548,10 @@ const StructureComparisonTaskContent: React.FC - + + {task?.id} + {TaskTypeMap?.[task?.type]} - {task?.id} - - - {TaskTypeMap?.[task?.type]} - - - + + , + color: '#1890FF', + }, + + // 新建作业 + [PageType.CREATE_SCHEDULES]: { + component: withConfirmModal(CreateSchedule), + icon: , + color: '#1890FF', + }, + // 视图创建页 [PageType.CREATE_VIEW]: { component: withConfirmModal(CreateViewPage), diff --git a/src/component/WindowManager/helper.ts b/src/component/WindowManager/helper.ts index 4fc306cb9..f016bb559 100644 --- a/src/component/WindowManager/helper.ts +++ b/src/component/WindowManager/helper.ts @@ -16,6 +16,8 @@ import { IPage, PageType } from '@/d.ts'; import { getTitleByParams } from '@/page/Workspace/components/TaskPage'; +import { getScheduleTitleByParams } from '@/page/Workspace/components/SchedulePage'; +import { getTitleByParams as getCreateScheduleTitleByParams } from '@/page/Workspace/components/CreateSchedule'; import { BatchCompilePage, OBClientPage, SQLPage } from '@/store/helper/page/pages'; import { SQLConfirmPage } from '@/store/helper/page/pages/create'; import { AnonymousPage } from '@/store/helper/page/pages/pl'; @@ -105,6 +107,12 @@ export function getPageTitleText(page: IPage) { case PageType.TASKS: { return getTitleByParams(params); } + case PageType.SCHEDULES: { + return getScheduleTitleByParams(params); + } + case PageType.CREATE_SCHEDULES: { + return title || getCreateScheduleTitleByParams(params); + } default: { return title; } diff --git a/src/constant/schedule.ts b/src/constant/schedule.ts new file mode 100644 index 000000000..9cf93499b --- /dev/null +++ b/src/constant/schedule.ts @@ -0,0 +1,77 @@ +import { ApprovalStatus } from '@/component/Schedule/interface'; +import { + SchedulePageType, + ScheduleStatus, + ScheduleType, + ScheduleActionsEnum, +} from '@/d.ts/schedule'; +import { formatMessage } from '@/util/intl'; + +export const SchedulePageTextMap = { + [SchedulePageType.ALL]: '所有作业', + [SchedulePageType.DATA_ARCHIVE]: formatMessage({ + id: 'odc.component.Task.helper.DataArchiving', + defaultMessage: '数据归档', + }), + [SchedulePageType.DATA_DELETE]: formatMessage({ + id: 'odc.component.Task.helper.DataCleansing', + defaultMessage: '数据清理', + }), + [SchedulePageType.SQL_PLAN]: formatMessage({ + id: 'odc.TaskManagePage.component.helper.SqlPlan', + defaultMessage: 'SQL 计划', + }), + [SchedulePageType.PARTITION_PLAN]: formatMessage({ + id: 'odc.TaskManagePage.component.TaskTable.PartitionPlan', + defaultMessage: '分区计划', + }), +}; + +export const ScheduleTextMap = { + [ScheduleType.DATA_ARCHIVE]: formatMessage({ + id: 'odc.component.Task.helper.DataArchiving', + defaultMessage: '数据归档', + }), + [ScheduleType.DATA_DELETE]: formatMessage({ + id: 'odc.component.Task.helper.DataCleansing', + defaultMessage: '数据清理', + }), + [ScheduleType.SQL_PLAN]: formatMessage({ + id: 'odc.TaskManagePage.component.helper.SqlPlan', + defaultMessage: 'SQL 计划', + }), + [ScheduleType.PARTITION_PLAN]: formatMessage({ + id: 'odc.TaskManagePage.component.TaskTable.PartitionPlan', + defaultMessage: '分区计划', + }), +}; + +export const ScheduleStatusTextMap = { + [ScheduleStatus.CREATING]: '创建中', + [ScheduleStatus.PAUSE]: '已禁用', + [ScheduleStatus.ENABLED]: '已启用', + [ScheduleStatus.TERMINATED]: '已终止', + [ScheduleStatus.COMPLETED]: '已完成', + [ScheduleStatus.EXECUTION_FAILED]: '执行超时', + [ScheduleStatus.DELETED]: '已删除', + [ScheduleStatus.CANCELED]: '已取消', +}; + +export const ScheduleActionsTextMap = { + [ScheduleActionsEnum.VIEW]: '查看', + [ScheduleActionsEnum.CLONE]: '克隆', + [ScheduleActionsEnum.SHARE]: '分享', + [ScheduleActionsEnum.STOP]: '终止', + [ScheduleActionsEnum.DISABLE]: '禁用', + [ScheduleActionsEnum.ENABLE]: '启用', + [ScheduleActionsEnum.EDIT]: '编辑', + [ScheduleActionsEnum.DELETE]: '删除', + [ScheduleActionsEnum.PASS]: '同意', + [ScheduleActionsEnum.REVOKE]: '撤销审批', + [ScheduleActionsEnum.REFUSE]: '拒绝', +}; + +export const ApprovalStatusTextMap = { + [ApprovalStatus.APPROVING]: '审批中', + [ApprovalStatus.APPROVE_FAILED]: '审批失败', +}; diff --git a/src/constant/scheduleTask.ts b/src/constant/scheduleTask.ts new file mode 100644 index 000000000..e5c6118c8 --- /dev/null +++ b/src/constant/scheduleTask.ts @@ -0,0 +1,47 @@ +import { ScheduleTaskStatus, ScheduleTaskActionsEnum, SubTaskType } from '@/d.ts/scheduleTask'; +import { formatMessage } from '@/util/intl'; + +export const ScheduleTaskStatusTextMap = { + [ScheduleTaskStatus.PREPARING]: '待调度', + [ScheduleTaskStatus.RUNNING]: '执行中', + [ScheduleTaskStatus.ABNORMAL]: '执行异常', + [ScheduleTaskStatus.PAUSING]: '暂停中', + [ScheduleTaskStatus.PAUSED]: '已暂停', + [ScheduleTaskStatus.RESUMING]: '恢复中', + [ScheduleTaskStatus.CANCELING]: '终止中', + [ScheduleTaskStatus.FAILED]: '执行失败', + [ScheduleTaskStatus.EXEC_TIMEOUT]: '执行超时', + [ScheduleTaskStatus.CANCELED]: '已终止', + [ScheduleTaskStatus.DONE]: '执行成功', +}; + +export const ScheduleTaskActionsTextMap = { + [ScheduleTaskActionsEnum.VIEW]: '查看', + [ScheduleTaskActionsEnum.SHARE]: '分享', + [ScheduleTaskActionsEnum.STOP]: '终止', + [ScheduleTaskActionsEnum.EXECUTE]: '执行', + [ScheduleTaskActionsEnum.PAUSE]: '暂停', + [ScheduleTaskActionsEnum.RESTORE]: '恢复', + [ScheduleTaskActionsEnum.RETRY]: '重试', +}; + +export const SubTypeTextMap = { + [SubTaskType.DATA_ARCHIVE]: formatMessage({ + id: 'odc.component.CommonDetailModal.TaskExecuteRecord.DataArchiving', + defaultMessage: '数据归档', + }), + [SubTaskType.PARTITION_PLAN]: '分区计划', + [SubTaskType.SQL_PLAN]: '数据库变更', + [SubTaskType.DATA_DELETE]: formatMessage({ + id: 'odc.component.CommonDetailModal.TaskExecuteRecord.DataCleansing', + defaultMessage: '数据清理', + }), //数据清理 + [SubTaskType.DATA_ARCHIVE_ROLLBACK]: formatMessage({ + id: 'odc.component.CommonDetailModal.TaskExecuteRecord.Rollback', + defaultMessage: '回滚', + }), + [SubTaskType.DATA_ARCHIVE_DELETE]: formatMessage({ + id: 'odc.component.CommonDetailModal.TaskExecuteRecord.SourceTableCleanup', + defaultMessage: '源表清理', + }), +}; diff --git a/src/constant/task.ts b/src/constant/task.ts new file mode 100644 index 000000000..c34849f30 --- /dev/null +++ b/src/constant/task.ts @@ -0,0 +1,87 @@ +import { TaskPageType } from '@/d.ts'; +import { formatMessage } from '@/util/intl'; +import { TaskActionsEnum, TaskGroup } from '@/d.ts/task'; + +export const TaskPageTextMap = { + [TaskPageType.ALL]: formatMessage({ + id: 'odc.src.component.Task.AllWorkOrders', + defaultMessage: '所有工单', + }), + [TaskPageType.EXPORT]: formatMessage({ + id: 'odc.components.TaskManagePage.Export', + defaultMessage: '导出', + }), + [TaskPageType.EXPORT_RESULT_SET]: formatMessage({ + id: 'odc.src.component.Task.ExportResultSet', + defaultMessage: '导出结果集', + }), + [TaskPageType.IMPORT]: formatMessage({ + id: 'odc.components.TaskManagePage.Import', + defaultMessage: '导入', + }), + [TaskPageType.DATAMOCK]: formatMessage({ + id: 'odc.components.TaskManagePage.AnalogData', + defaultMessage: '模拟数据', + }), + [TaskPageType.ASYNC]: formatMessage({ + id: 'odc.components.TaskManagePage.DatabaseChanges', + defaultMessage: '数据库变更', + }), + [TaskPageType.MULTIPLE_ASYNC]: formatMessage({ + id: 'src.component.Task.1EDC83CC', + defaultMessage: '多库变更', + }), + [TaskPageType.LOGICAL_DATABASE_CHANGE]: formatMessage({ + id: 'src.component.Task.A7954C70', + defaultMessage: '逻辑库变更', + }), + [TaskPageType.SHADOW]: formatMessage({ + id: 'odc.TaskManagePage.component.TaskTable.ShadowTableSynchronization', + defaultMessage: '影子表同步', + }), + [TaskPageType.STRUCTURE_COMPARISON]: formatMessage({ + id: 'src.component.Task.223677D8', + defaultMessage: '结构比对', + }), + [TaskPageType.ONLINE_SCHEMA_CHANGE]: formatMessage({ + id: 'odc.component.Task.helper.LockFreeStructureChange', + defaultMessage: '无锁结构变更', + }), + [TaskPageType.APPLY_PROJECT_PERMISSION]: '项目权限', + [TaskPageType.APPLY_DATABASE_PERMISSION]: '库权限', + [TaskPageType.APPLY_TABLE_PERMISSION]: '表/视图权限', +}; + +export const TaskGroupTextMap = { + [TaskGroup.Other]: '', + [TaskGroup.DataExport]: formatMessage({ + id: 'odc.component.Task.helper.DataExport', + defaultMessage: '数据导出', + }), + [TaskGroup.DataChanges]: formatMessage({ + id: 'odc.component.Task.helper.DataChanges', + defaultMessage: '数据变更', + }), + [TaskGroup.AccessRequest]: formatMessage({ + id: 'odc.src.component.Task.AccessRequest', + defaultMessage: '权限申请', + }), +}; + +export const TaskActionsTextMap = { + [TaskActionsEnum.VIEW]: '查看', + [TaskActionsEnum.CLONE]: '克隆', + [TaskActionsEnum.SHARE]: '分享', + [TaskActionsEnum.STOP]: '终止', + [TaskActionsEnum.ROLLBACK]: '回滚', + [TaskActionsEnum.EXECUTE]: '执行', + [TaskActionsEnum.PASS]: '通过', + [TaskActionsEnum.AGAIN]: '重试', + [TaskActionsEnum.DOWNLOAD]: '下载', + [TaskActionsEnum.REJECT]: '拒绝', + [TaskActionsEnum.DOWNLOAD_SQL]: '下载 SQL', + [TaskActionsEnum.STRUCTURE_COMPARISON]: '发起结构同步', + [TaskActionsEnum.OPEN_LOCAL_FOLDER]: '打开文件夹', + [TaskActionsEnum.DOWNLOAD_VIEW_RESULT]: '下载查询结果', + [TaskActionsEnum.VIEW_RESULT]: '查询结果', +}; diff --git a/src/constant/triangularization.ts b/src/constant/triangularization.ts new file mode 100644 index 000000000..41eb45943 --- /dev/null +++ b/src/constant/triangularization.ts @@ -0,0 +1,37 @@ +import { TaskPageType, TaskStatus } from '@/d.ts'; +import { SchedulePageType, ScheduleStatus } from '@/d.ts/schedule'; + +// 三方化常量 + +/** 允许终止的工单状态 */ +export const taskStatusThatCanBeTerminate = [ + TaskStatus.CREATING, + TaskStatus.APPROVING, + TaskStatus.ENABLED, + TaskStatus.PAUSE, + TaskStatus.EXECUTING, + TaskStatus.WAIT_FOR_EXECUTION, + TaskStatus.CREATED, +]; + +/** 允许终止的工单类型 */ +export const taskTypeThatCanBeTerminate = Object.keys(TaskPageType)?.filter( + (item) => + ![TaskPageType.ALL, TaskPageType.STRUCTURE_COMPARISON, TaskPageType.MULTIPLE_ASYNC].includes( + item as TaskPageType, + ), +); + +/** 允许导出的作业状态 */ +export const scheduleStatusThatCanBeExport = Object.keys(ScheduleStatus); + +/** 允许终止的作业类型 */ +export const scheduleThatCanBeExport = [ + SchedulePageType.SQL_PLAN, + SchedulePageType.DATA_ARCHIVE, + SchedulePageType.DATA_DELETE, + SchedulePageType.PARTITION_PLAN, +]; + +/** 允许终止的作业状态 */ +export const SchedulestatusThatCanBeTerminate = [ScheduleStatus.PAUSE, ScheduleStatus.ENABLED]; diff --git a/src/d.ts/_index.ts b/src/d.ts/_index.ts index 129be9793..8be6d3704 100644 --- a/src/d.ts/_index.ts +++ b/src/d.ts/_index.ts @@ -33,6 +33,7 @@ export enum IPageType { Project_User = 'user', Project_Setting = 'setting', Project_Task = 'task', + Project_Schedule = 'schedule', Project_Notification = 'notification', Datasource = 'datasource', Datasource_info = 'info', @@ -41,6 +42,7 @@ export enum IPageType { Datasource_obclient = 'obclient', Large_Model = 'largeModel', Task = 'task', + Schedule = 'schedule', Auth = 'auth', Auth_User = 'user', Auth_Role = 'role', diff --git a/src/d.ts/importTask.ts b/src/d.ts/importTask.ts index 1155ad241..f309a3dff 100644 --- a/src/d.ts/importTask.ts +++ b/src/d.ts/importTask.ts @@ -1,6 +1,7 @@ import { formatMessage } from '@/util/intl'; import { ConnectType, IConnection, TaskType } from '.'; import { ODCCloudProvider } from './migrateTask'; +import { ScheduleType } from './schedule'; export enum ScheduleNonImportableType { LACK_OF_INSTANCE = 'LACK_OF_INSTANCE', @@ -59,7 +60,7 @@ export interface IImportScheduleTaskView { * Project name of the system before export */ originProjectName: string; - type: TaskType; + type: TaskType | ScheduleType; databaseView: IImportDatabaseView; targetDatabaseView: IImportDatabaseView; } diff --git a/src/d.ts/index.ts b/src/d.ts/index.ts index cebd272e5..8600b8ff1 100644 --- a/src/d.ts/index.ts +++ b/src/d.ts/index.ts @@ -992,6 +992,8 @@ export enum PageType { IMPORT = 'IMPORT', EXPORT = 'EXPORT', TASKS = 'TASKS', + SCHEDULES = 'SCHEDULES', + CREATE_SCHEDULES = 'CREATE_SCHEDULES', OB_CLIENT = 'OB_CLIENT', CREATE_TRIGGER = 'CREATE_TRIGGER', // 触发器创建页 CREATE_TRIGGER_SQL = 'CREATE_TRIGGER_SQL', // 触发器创建页 @@ -2083,34 +2085,34 @@ export enum TransferType { EXPORT = 'EXPORT', } -export enum TaskPageScope { - CREATED_BY_CURRENT_USER = 'createdByCurrentUser', - APPROVE_BY_CURRENT_USER = 'approveByCurrentUser', -} - export enum TaskPageType { + /** 所有工单 */ ALL = 'ALL', + /** 导入 */ IMPORT = 'IMPORT', + /** 导出 */ EXPORT = 'EXPORT', + /** 模拟数据 */ DATAMOCK = 'MOCKDATA', + /** 数据库变更 */ ASYNC = 'ASYNC', - PARTITION_PLAN = 'PARTITION_PLAN', - SQL_PLAN = 'SQL_PLAN', - DATASAVE = 'DATASAVE', + /** 影子表同步 */ SHADOW = 'SHADOWTABLE_SYNC', - CREATED_BY_CURRENT_USER = 'createdByCurrentUser', - APPROVE_BY_CURRENT_USER = 'approveByCurrentUser', - ALTER_SCHEDULE = 'ALTER_SCHEDULE', - SENSITIVE_COLUMN = 'SENSITIVE_COLUMN', - DATA_ARCHIVE = 'DATA_ARCHIVE', + /** 无锁结构变更 */ ONLINE_SCHEMA_CHANGE = 'ONLINE_SCHEMA_CHANGE', - DATA_DELETE = 'DATA_DELETE', + /** 导出结果集 */ EXPORT_RESULT_SET = 'EXPORT_RESULT_SET', + /** 申请项目权限 */ APPLY_PROJECT_PERMISSION = 'APPLY_PROJECT_PERMISSION', + /** 申请库权限 */ APPLY_DATABASE_PERMISSION = 'APPLY_DATABASE_PERMISSION', + /** 申请表/视图权限 */ APPLY_TABLE_PERMISSION = 'APPLY_TABLE_PERMISSION', + /** 结构比对 */ STRUCTURE_COMPARISON = 'STRUCTURE_COMPARISON', + /** 多库变更 */ MULTIPLE_ASYNC = 'MULTIPLE_ASYNC', + /** 逻辑库变更 */ LOGICAL_DATABASE_CHANGE = 'LOGICAL_DATABASE_CHANGE', } @@ -2123,10 +2125,6 @@ export enum TaskType { DATAMOCK = 'MOCKDATA', /** 数据库变更 */ ASYNC = 'ASYNC', - /** 分区计划 */ - PARTITION_PLAN = 'PARTITION_PLAN', - /** sql 计划 */ - SQL_PLAN = 'SQL_PLAN', /** 计划变更(调度任务) */ ALTER_SCHEDULE = 'ALTER_SCHEDULE', /** 影子表同步 */ @@ -2135,14 +2133,10 @@ export enum TaskType { DATA_SAVE = 'DATA_SAVE', /** 权限申请 (疑似弃用) */ PERMISSION_APPLY = 'PERMISSION_APPLY', - /** 数据归档 */ - DATA_ARCHIVE = 'DATA_ARCHIVE', /** (疑似弃用?) */ MIGRATION = 'DATA_ARCHIVE', /** 无锁结构变更 */ ONLINE_SCHEMA_CHANGE = 'ONLINE_SCHEMA_CHANGE', - /** 数据清理 */ - DATA_DELETE = 'DATA_DELETE', /** 导出结果集 */ EXPORT_RESULT_SET = 'EXPORT_RESULT_SET', /** 申请项目权限 */ @@ -2525,16 +2519,6 @@ export interface TaskRecord

{ project: IProject; } -export interface ICycleSubTaskRecord { - createTime: number; - id: number; - jobGroup: TaskPageType.DATA_ARCHIVE | TaskPageType.SQL_PLAN; - jobName: string; - resultJson: string; - status: TaskStatus; - updateTime: number; -} - export interface ISubTaskRecord { createTime: number; fireTime: number; @@ -2550,6 +2534,38 @@ export interface ISubTaskRecord { export interface ISubTaskRecords { tasks: ISubTaskRecord[]; + sqlExecutionResultMap: Record; +} + +export interface sqlExecutionResultMap { + completed: boolean; + order: number; + status: string; + id: number; + result: ILogicalsqlExecutionResult; + physicalDatabase: IDatabase; +} + +interface ILogicalsqlExecutionResult { + logicalDatabaseId: number; + errorCode: number; + executeSql: string; + originSql: string; + physicalDatabaseId: number; + flowInstanceId: number; + status: string; + traceId: string; + timer: { + name: string; + startTimeMillis: number; + totalDurationMicroseconds: number; + stages: { + stageName: string; + startTimeMillis: number; + subStages: any[]; + totalDurationMicroseconds: number; + }[]; + }; } export interface IDatasourceUser { @@ -2561,8 +2577,6 @@ export type TaskRecordParameters = | IAsyncTaskParams | IMockDataParams | IPermissionTaskParams - | IPartitionPlanParams - | ISQLPlanTaskParams | IAlterScheduleTaskParams | IResultSetExportTaskParams | IApplyPermissionTaskParams @@ -2612,6 +2626,9 @@ export enum ScheduleChangeStatus { CHANGING = 'CHANGING', SUCCESS = 'SUCCESS', FAILED = 'FAILED', + APPROVE_CANCELED = 'APPROVE_CANCELED', + APPROVE_EXPIRED = 'APPROVE_EXPIRED', + APPROVE_REJECTED = 'APPROVE_REJECTED', } export enum MigrationInsertAction { @@ -2620,9 +2637,9 @@ export enum MigrationInsertAction { } export enum ShardingStrategy { + AUTO = 'AUTO', FIXED_LENGTH = 'FIXED_LENGTH', MATCH = 'MATCH', - AUTO = 'AUTO', } export enum SyncTableStructureEnum { @@ -2640,58 +2657,6 @@ export interface IDLMJobParametersTables { joinTableConfigs?: IJoinTableConfigs[]; } -export interface IDataArchiveJobParameters { - deleteAfterMigration: boolean; - deleteTemporaryTable?: boolean; - name: string; - sourceDatabaseId: number; - sourceDatabase?: IDatabase; - targetDataBaseId: number; - targetDatabase: IDatabase; - migrationInsertAction?: MigrationInsertAction; - shardingStrategy?: ShardingStrategy; - deleteByUniqueKey?: boolean; - rateLimit?: { - rowLimit?: number; - dataSizeLimit?: number; - }; - tables: IDLMJobParametersTables[]; - variables: { - name: string; - pattern: string; - }[]; - timeoutMillis: number; - syncTableStructure: SyncTableStructureEnum[]; - dirtyRowAction: DirtyRowActionEnum; - maxAllowedDirtyRowCount: number; - fullDatabase: boolean; -} - -export interface IDataClearJobParameters { - deleteAfterMigration: boolean; - name: string; - databaseId: number; - database?: IDatabase; - deleteByUniqueKey?: boolean; - rateLimit?: { - rowLimit?: number; - dataSizeLimit?: number; - }; - tables: IDLMJobParametersTables[]; - variables: { - name: string; - pattern: string; - }[]; - timeoutMillis: number; - needCheckBeforeDelete: boolean; - targetDatabaseId?: number; - targetDatabase?: IDatabase; - shardingStrategy?: ShardingStrategy; - dirtyRowAction: DirtyRowActionEnum; - maxAllowedDirtyRowCount: number; - fullDatabase: boolean; -} - export interface ISqlPlayJobParameters { delimiter: string; errorStrategy: string; @@ -2756,17 +2721,6 @@ export interface ICycleTaskStatRecord { }[]; } -export interface ICycleSubTaskDetailRecord { - createTime: number; - id: number; - jobGroup: TaskType.DATA_ARCHIVE | TaskType.SQL_PLAN; - jobName: string; - resultJson: string; - status: SubTaskStatus; - updateTime: number; - executionDetails: string; -} - export interface ICycleTaskJobRecord { createTime: number; executionDetails: T; @@ -3057,8 +3011,6 @@ export interface IMockDataParams { dbMode?: ConnectionMode; } -export interface IPartitionPlanParams extends IPartitionPlan {} - export interface ICycleTaskTriggerConfig { cronExpression?: string; days?: number[]; @@ -3067,22 +3019,6 @@ export interface ICycleTaskTriggerConfig { triggerStrategy?: TaskExecStrategy; } -export interface ISQLPlanTaskParams { - taskType: TaskType.SQL_PLAN; - operationType: TaskOperationType; - scheduleTaskParameters: { - timeoutMillis: number; - errorStrategy: string; - sqlContent: string; - sqlObjectIds: string[]; - sqlObjectNames: string[]; - delimiter: string; - queryLimit: number; - }; - - triggerConfig: ICycleTaskTriggerConfig; -} - export interface IAlterScheduleTaskParams { taskType: TaskType.ALTER_SCHEDULE; taskId: number; @@ -3107,27 +3043,6 @@ export interface IAlterScheduleTaskParams { }; } -export interface IDataArchiveTaskParams { - type: TaskType.DATA_ARCHIVE; - taskId: number; - operationType: TaskOperationType; - allowConcurrent: boolean; - description: string; - misfireStrategy: string; - triggerConfig: ICycleTaskTriggerConfig; - scheduleTaskParameters: { - deleteAfterMigration: boolean; - name: string; - sourceDatabaseId: number; - targetDataBaseId: number; - tables: IDLMJobParametersTables[]; - variables: { - name: string; - pattern: string; - }[]; - }; -} - export interface IStructureComparisonTaskParams { comparisonScope: EComparisonScope; sourceDatabaseId: number; @@ -3160,6 +3075,7 @@ export enum TaskOperationType { PAUSE = 'PAUSE', TERMINATE = 'TERMINATE', RESUME = 'RESUME', + DELETE = 'DELETE', } export enum IFlowTaskType { @@ -3199,10 +3115,6 @@ export interface ITaskFlowNode { export type TaskDetail

= TaskRecord

; -export interface IIPartitionPlanTaskDetail extends TaskDetail { - nextFireTimes?: number[]; -} - export type CycleTaskDetail = ICycleTaskRecord; export type DataArchiveTaskDetail = IDataArchiveTaskRecord; @@ -3223,6 +3135,7 @@ export enum TaskStatus { APPROVAL_EXPIRED = 'APPROVAL_EXPIRED', // 审批过期 WAIT_FOR_EXECUTION = 'WAIT_FOR_EXECUTION', // 待执行 WAIT_FOR_EXECUTION_EXPIRED = 'WAIT_FOR_EXECUTION_EXPIRED', // 等待执行过期 + EXECUTION_SUCCEEDED_WITH_ERRORS = 'EXECUTION_SUCCEEDED_WITH_ERRORS', // 执行成功,但跳过错误 EXECUTING = 'EXECUTING', // 执行中 EXECUTION_SUCCEEDED = 'EXECUTION_SUCCEEDED', // 执行成功 EXECUTION_FAILED = 'EXECUTION_FAILED', // 执行失败 @@ -3747,21 +3660,6 @@ export interface IPartitionKeyConfig { partitionKeyInvokerParameters: Record; } -export interface IPartitionPlan { - creationTrigger: ICycleTaskTriggerConfig; - createTriggerNextFireTimes: number[]; - droppingTrigger: ICycleTaskTriggerConfig; - dropTriggerNextFireTimes: number[]; - databaseId: number; - enabled: boolean; - id: number; - maxErrors: number; - timeoutMillis: number; - partitionTableConfig: IPartitionTableConfig; - partitionTableConfigs: IPartitionTableConfig[]; - errorStrategy: TaskErrorStrategy; -} - export interface IPartitionPlanTable { DDL: string; columns: { diff --git a/src/d.ts/schedule.ts b/src/d.ts/schedule.ts new file mode 100644 index 000000000..d55eadf47 --- /dev/null +++ b/src/d.ts/schedule.ts @@ -0,0 +1,342 @@ +import { IDatabase } from './database'; +import { IProject, ProjectRole } from './project'; +import { + ICycleTaskTriggerConfig, + IPartitionTableConfig, + TaskErrorStrategy, + ShardingStrategy, + SyncTableStructureEnum, +} from '@/d.ts'; +export enum ScheduleType { + /** 数据归档 */ + DATA_ARCHIVE = 'DATA_ARCHIVE', + /** 数据清理 */ + DATA_DELETE = 'DATA_DELETE', + /** 分区计划 */ + PARTITION_PLAN = 'PARTITION_PLAN', + /** sql 计划 */ + SQL_PLAN = 'SQL_PLAN', +} + +/** + * 操作列自定义权限(前端) + */ +export enum IOperationTypeRole { + /** 项目开发者 */ + PROJECT_DEVELOPER = 'DEVELOPER', + /** 项目DBA */ + PROJECT_DBA = 'DBA', + /** 项目Owner */ + PROJECT_OWNER = 'OWNER', + /** 项目安全管理员 */ + PROJECT_SECURITY_ADMINISTRATOR = 'SECURITY_ADMINISTRATOR', + /** 项目参与者 */ + PROJECT_PARTICIPANT = 'PARTICIPANT', + /** 创建人 */ + CREATOR = 'CREATOR', + /** 可审批人 */ + APPROVER = 'APPROVER', +} + +export enum ScheduleViewType { + /** 调度视角*/ + ScheduleView = 'scheduleView', + /** 执行视角*/ + ExecutionView = 'executionView', +} + +export enum ScheduleDetailType { + /** 基本信息 */ + INFO = 'INFO', + /** 执行记录 */ + EXECUTE_RECORD = 'EXECUTE_RECORD', + /** 操作记录 */ + OPERATION_RECORD = 'OPERATION_RECORD', +} + +export enum SchedulePageType { + ALL = 'ALL_Schedule', + /** 数据归档 */ + DATA_ARCHIVE = 'DATA_ARCHIVE', + /** 数据清理 */ + DATA_DELETE = 'DATA_DELETE', + /** 分区计划 */ + PARTITION_PLAN = 'PARTITION_PLAN', + /** SQL计划 */ + SQL_PLAN = 'SQL_PLAN', +} + +export enum ScheduleStatus { + /** 创建中 */ + CREATING = 'CREATING', + /** 已禁用 */ + PAUSE = 'PAUSE', + /** 已启用 */ + ENABLED = 'ENABLED', + /** 已终止 */ + TERMINATED = 'TERMINATED', + /** 已完成 */ + COMPLETED = 'COMPLETED', + /** 执行超时 */ + EXECUTION_FAILED = 'EXECUTION_FAILED', + /** 已删除 */ + DELETED = 'DELETED', + /** 已取消 */ + CANCELED = 'CANCELED', +} + +export enum ScheduleActionsEnum { + /** 查看 */ + VIEW = 'VIEW', + /** 克隆 */ + CLONE = 'CLONE', + /** 分享 */ + SHARE = 'SHARE', + /** 终止 */ + STOP = 'STOP', + /** 禁用 */ + DISABLE = 'DISABLE', + /** 启用 */ + ENABLE = 'ENABLE', + /** 编辑 */ + EDIT = 'EDIT', + /** 删除 */ + DELETE = 'DELETE', + /** 通过 */ + PASS = 'PASS', + /** 撤销 */ + REVOKE = 'REVOKE', + /** 拒绝 */ + REFUSE = 'REFUSE', +} + +export interface IScheduleRecord { + allowConcurrent: boolean; + createTime: number; + creator: { + id: number; + name: string; + accountName: string; + roleNames: string[]; + }; + candidateApprovers?: { + id: number; + name: string; + accountName: string; + }[]; + currentUserResourceRoles: ProjectRole[]; + approvable?: boolean; + approveInstanceId?: number; + description?: string; + nextFireTimes: number[]; + parameters?: T; + project?: IProject; + projectId: number; + scheduleId: number; + scheduleName?: string; + database?: IDatabase; + status: ScheduleStatus; + type: ScheduleType; + updateTime: number; + triggerConfig: ICycleTaskTriggerConfig; + misfireStrategy?: string; + flowInstanceId?: number; + attributes?: { + databaseInfo?: IDatabase; + sourceDataBaseInfo?: IDatabase; + targetDataBaseInfo?: IDatabase; + }; + latestChangedLogId?: number; +} +export type ScheduleRecordParameters = + | IPartitionPlan + | ISqlPlanParameters + | IDataClearParameters + | IDataArchiveParameters; + +export type IPartitionPlan = { + creationTrigger: ICycleTaskTriggerConfig; + createTriggerNextFireTimes: number[]; + droppingTrigger: ICycleTaskTriggerConfig; + dropTriggerNextFireTimes: number[]; + databaseId: number; + databaseInfo: IDatabase; + enabled: boolean; + id: number; + flowInstanceId: number; + taskId: number; + // maxErrors: number; + timeoutMillis: number; + // partitionTableConfig: IPartitionTableConfig; + partitionTableConfigs: IPartitionTableConfig[]; + errorStrategy: TaskErrorStrategy; +}; + +export type ISqlPlanParameters = { + databaseId: number; + databaseInfo: IDatabase; + generateRollbackPlan: string; + markAsFailedWhenAnyErrorsHappened: boolean; + modifyTimeoutIfTimeConsumingSqlExists: boolean; + parentScheduleType: ScheduleType; + sqlContent: string; + sqlObjectNames: string[]; + sqlObjectIds: string[]; + timeoutMillis: number; + errorStrategy: TaskErrorStrategy; + delimiter: string; + queryLimit: number; + retryTimes: number; + retryIntervalMillis: number; + riskLevelIndex: number; + rollbackSqlContent: null; + rollbackSqlObjectIds: null; + rollbackSqlObjectNames: null; +}; + +export type IDataClearParameters = { + cpuLimit: number; + database: IDatabase; + databaseId: number; + deleteByUniqueKey: boolean; + deleteTemporaryTable: boolean; + dirtyRowAction: string; + fullDatabase: boolean; + maxAllowedDirtyRowCount: string; + needCheckBeforeDelete: boolean; + needPrintSqlTrace: boolean; + queryTimeout: number; + rateLimit: { batchSize: number; dataSizeLimit: number; orderId: number; rowLimit: number }; + readThreadCount: number; + scanBatchSize: number; + shardingStrategy: ShardingStrategy; + tables: any[]; + targetDatabase?: IDatabase; + targetDatabaseId: number; + timeoutMillis: number; + variables: { + name: string; + pattern: string; + }[]; + writeThreadCount: number; +}; + +export type IDataArchiveParameters = { + cpuLimit: number; + deleteAfterMigration: boolean; + deleteTemporaryTable: boolean; + dirtyRowAction: string; + fullDatabase: boolean; + maxAllowedDirtyRowCount: string; + migrationInsertAction: string; + needPrintSqlTrace: string; + queryTimeout: number; + rateLimit: { + batchSize: number; + dataSizeLimit: number; + orderId: number; + rowLimit: number; + }; + readThreadCount: number; + scanBatchSize: number; + shardingStrategy: string; + sourceDataSourceName: string; + sourceDatabase: IDatabase; + sourceDatabaseId: number; + sourceDatabaseName: string; + syncTableStructure: SyncTableStructureEnum[]; + tables: any[]; + targetDataBaseId: number; + targetDataSourceName: string; + targetDatabase: IDatabase; + targetDatabaseName: string; + timeoutMillis: number; + variables: any[]; + writeThreadCount: number; +}; + +export type createScheduleRecord = { + /** 编辑时传入 */ + id?: number; + name?: string; + type?: ScheduleType; + parameters?: T; + triggerConfig?: ICycleTaskTriggerConfig; + description?: string; +}; + +export type createSchedueleParameters = + | createPartitionPlanParameters + | createSqlPlanParameters + | createDataDeleteParameters + | createDataArchiveParameters; + +export type createPartitionPlanParameters = { + databaseId: string; + enabled: boolean; + partitionTableConfigs: IPartitionTableConfig[]; + creationTrigger: ICycleTaskTriggerConfig; + droppingTrigger?: ICycleTaskTriggerConfig; + timeoutMillis: number; + errorStrategy: TaskErrorStrategy; + /** 编辑时传入 */ + id?: number; +}; + +export type createSqlPlanParameters = { + databaseId: number; + sqlContent: string; + sqlObjectNames: string[]; + sqlObjectIds: string[]; + timeoutMillis: number; + errorStrategy: TaskErrorStrategy; + queryLimit: number; + delimiter: string; + modifyTimeoutIfTimeConsumingSqlExists: boolean; +}; + +export type createDataDeleteParameters = { + databaseId: number; + deleteByUniqueKey?: boolean; + fullDatabase?: boolean; + needCheckBeforeDelete: boolean; + rateLimit?: { + rowLimit: number; + dataSizeLimit: number; + }; + shardingStrategy?: ShardingStrategy; + targetDatabaseId?: number; + tables: { + tableName: string; + conditionExpression: string; + targetTableName: string; + }[]; + timeoutMillis: number; + triggerConfig: ICycleTaskTriggerConfig; + variables: { + name: string; + pattern: string; + }[]; +}; + +export type createDataArchiveParameters = { + deleteAfterMigration: boolean; + fullDatabase: boolean; + migrationInsertAction: string; + rateLimit?: { + rowLimit: number; + dataSizeLimit: number; + }; + shardingStrategy?: ShardingStrategy; + syncTableStructure: SyncTableStructureEnum[]; + tables: { + tableName: string; + conditionExpression: string; + targetTableName: string; + }[]; + targetDataBaseId: number; + timeoutMillis: number; + variables: any[]; + sourceDatabaseId: number; + triggerConfig: ICycleTaskTriggerConfig; +}; diff --git a/src/d.ts/scheduleTask.ts b/src/d.ts/scheduleTask.ts new file mode 100644 index 000000000..62628f075 --- /dev/null +++ b/src/d.ts/scheduleTask.ts @@ -0,0 +1,114 @@ +import { ScheduleType } from './schedule'; +import { IDatabase } from './database'; +import { IProject, ProjectRole } from './project'; + +export enum ScheduleTaskStatus { + /** 待调度 */ + PREPARING = 'PREPARING', + /** 执行中 */ + RUNNING = 'RUNNING', + // task not work, but can be recovered + /** 执行异常 */ + ABNORMAL = 'ABNORMAL', + // pausing or paused or resuming only support for task who implement restart logic + // that means task must save checkpoint for it's recovery + /** 暂停中 */ + PAUSING = 'PAUSING', + /** 已暂停 */ + PAUSED = 'PAUSED', + /** 恢复中 */ + RESUMING = 'RESUMING', + // task is canceling, that will transfer to CANCELED status + /** 终止中 */ + CANCELING = 'CANCELING', + // the following is terminate states + /** 执行失败 */ + FAILED = 'FAILED', + /** 执行超时 */ + EXEC_TIMEOUT = 'EXEC_TIMEOUT', + /** 已终止 */ + CANCELED = 'CANCELED', + /** 执行成功 */ + DONE = 'DONE', +} + +export enum ScheduleTaskActionsEnum { + /** 查看 */ + VIEW = 'VIEW', + /** 分享 */ + SHARE = 'SHARE', + /** 终止 */ + STOP = 'STOP', + /** 执行 */ + EXECUTE = 'EXECUTE', + /**暂停(数据归档、数据清理)*/ + PAUSE = 'PAUSE', + /** 恢复(数据归档、数据清理)*/ + RESTORE = 'RESTORE', + /** 重试(数据归档、数据清理)*/ + RETRY = 'RETRY', +} + +export interface scheduleTask { + createTime: number; + id: number; + jobGroup: ScheduleType; + status: ScheduleTaskStatus; + type: SubTaskType; + updateTime: number; + executionDetails: any; + project: IProject; + currentUserResourceRoles?: ProjectRole[]; + scheduleId?: number; + scheduleName?: string; + lastExecutionTime?: number; + jobName?: string; + parameters: any; + creator?: { + id: number; + name: string; + accountName: string; + roleNames: string[]; + }; + attributes?: { + sourceDataBaseInfo?: IDatabase; + targetDataBaseInfo?: IDatabase; + }; +} + +export enum SubTaskType { + /** 数据归档 */ + DATA_ARCHIVE = 'DATA_ARCHIVE', + /** 数据清理 */ + DATA_DELETE = 'DATA_DELETE', + /** 分区计划 */ + PARTITION_PLAN = 'PARTITION_PLAN', + /** SQL计划 */ + SQL_PLAN = 'SQL_PLAN', + /** 回滚 */ + DATA_ARCHIVE_ROLLBACK = 'DATA_ARCHIVE_ROLLBACK', + /** 源表清理 */ + DATA_ARCHIVE_DELETE = 'DATA_ARCHIVE_DELETE', +} + +export interface IScheduleTaskRecord { + createTime: number; + executionDetails: string; + fireTime: number; + id: number; + parameters: T; + status: ScheduleTaskStatus; + type: SubTaskType; + updateTime: number; +} + +export enum ScheduleTaskDetailType { + /** 基本信息 */ + INFO = 'INFO', + /** 执行结果 */ + EXECUTE_RESULT = 'EXECUTE_RESULT', + /** 操作记录 */ + OPERATION_RECORD = 'OPERATION_RECORD', + /** 任务日志 */ + LOG = 'LOG', +} diff --git a/src/d.ts/task.ts b/src/d.ts/task.ts index e87534df9..5996b3320 100644 --- a/src/d.ts/task.ts +++ b/src/d.ts/task.ts @@ -55,3 +55,50 @@ export interface IStructrueComparisonDetail { sourceObjectDdl: string; targetObjectDdl: string; } + +export enum TaskGroup { + Other = 'Other', + /** 数据导出 */ + DataExport = 'DataExport', + /** 数据变更 */ + DataChanges = 'DataChanges', + /** 权限申请 */ + AccessRequest = 'AccessRequest', +} + +export enum TaskActionsEnum { + /** 执行 */ + EXECUTE = 'EXECUTE', + /** 通过 */ + PASS = 'PASS', + /** 拒绝 */ + REJECT = 'REJECT', + /** 重试 */ + AGAIN = 'AGAIN', + /** 下载 */ + DOWNLOAD_SQL = 'DOWNLOAD_SQL', + + // 以下在列表页会放到下拉菜单里 + /** 查看 */ + VIEW = 'VIEW', + /** 克隆 */ + CLONE = 'CLONE', + /** 分享 */ + SHARE = 'SHARE', + /** 回滚 */ + ROLLBACK = 'ROLLBACK', + /** 终止 */ + STOP = 'STOP', + + // 以下详情页才会展示 + /** 下载 SQL */ + DOWNLOAD = 'DOWNLOAD', + /** 发起结构同步 */ + STRUCTURE_COMPARISON = 'STRUCTURE_COMPARISON', + /** 打开文件夹 */ + OPEN_LOCAL_FOLDER = 'OPEN_LOCAL_FOLDER', + /** 下载查询结果 */ + DOWNLOAD_VIEW_RESULT = 'DOWNLOAD_VIEW_RESULT', + /** 查询结果 */ + VIEW_RESULT = 'VIEW_RESULT', +} diff --git a/src/layout/SpaceContainer/Sider/index.tsx b/src/layout/SpaceContainer/Sider/index.tsx index 2d5b4de28..fc997a94f 100644 --- a/src/layout/SpaceContainer/Sider/index.tsx +++ b/src/layout/SpaceContainer/Sider/index.tsx @@ -22,6 +22,7 @@ import { TaskStore } from '@/store/task'; import { ReactComponent as LinkOutlined } from '@/svgr/icon_connection.svg'; import { ReactComponent as TaskSvg } from '@/svgr/icon_task.svg'; import { ReactComponent as NewOpenSvg } from '@/svgr/newopen.svg'; +import { ReactComponent as ScheduleSvg } from '@/svgr/icon_schedule.svg'; import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; import tracert from '@/util/tracert'; @@ -152,6 +153,15 @@ const Sider: React.FC = function (props) { } /> + + + { const { statusType } = ConsoleTextConfig.schdules; const { successEnabledCount } = progress || {}; @@ -25,17 +25,11 @@ const ScheduleItem = ({ title, progress, type }) => { label={ { - const baseRoute = `/${IPageType.Task}?task=${type}`; - - if (type === TaskType.PARTITION_PLAN) { - navigate(baseRoute + `&status=${TaskStatus.EXECUTION_SUCCEEDED}`); - } else { - navigate( - baseRoute + `&status=${TaskStatus.ENABLED}&filtered=${TaskExecStrategy.CRON}`, - ); - } - }} + onClick={() => + navigate( + `/${IPageType.Schedule}?scheduleType=${type}&scheduleStatus=${ScheduleStatus.ENABLED}`, + ) + } > {formatMessage({ id: 'src.page.Console.components.ScheduleItem.4E8811DF', diff --git a/src/page/Console/const.ts b/src/page/Console/const.ts index 93e2b02b1..f847f6c52 100644 --- a/src/page/Console/const.ts +++ b/src/page/Console/const.ts @@ -1,5 +1,5 @@ import { formatMessage } from '@/util/intl'; -import { TaskPageType } from '@/d.ts'; +import { ScheduleType } from '@/d.ts/schedule'; import { title } from 'process'; export enum EQuickStartRole { @@ -43,10 +43,10 @@ export const ConsoleTextConfig = { ], scheduleType: [ - TaskPageType.DATA_ARCHIVE, - TaskPageType.DATA_DELETE, - TaskPageType.PARTITION_PLAN, - TaskPageType.SQL_PLAN, + ScheduleType.DATA_ARCHIVE, + ScheduleType.DATA_DELETE, + ScheduleType.PARTITION_PLAN, + ScheduleType.SQL_PLAN, ], }, quickStart: { diff --git a/src/page/Console/index.tsx b/src/page/Console/index.tsx index 1eb4f9537..40d66ef9e 100644 --- a/src/page/Console/index.tsx +++ b/src/page/Console/index.tsx @@ -16,7 +16,7 @@ import { areaLayout, ConsoleTextConfig, EQuickStartRole, gridConfig } from './co import styles from './index.less'; import RecentlyDatabase from './components/RecentlyDatabase'; import { useNavigate } from '@umijs/max'; -import { getScheduleStat } from '@/common/network/task'; +import { getScheduleStat } from '@/common/network/schedule'; import { ICycleTaskStatParam, TaskPageType } from '@/d.ts'; import login from '@/store/login'; import setting from '@/store/setting'; diff --git a/src/page/Gateway/task.ts b/src/page/Gateway/task.ts index 4fbbb70eb..00e4cf5a0 100644 --- a/src/page/Gateway/task.ts +++ b/src/page/Gateway/task.ts @@ -45,7 +45,7 @@ export const action = async (actionData: ITaskAction) => { if (!task) { return 'Get Task Failed'; } - taskStore.changeTaskManageVisible(true, TaskPageType.ALL, undefined, taskId, task?.type); + taskStore.changeTaskManageVisible(true, TaskPageType.ALL, taskId, task?.type); history.push('/project'); return; }; diff --git a/src/page/Project/Schedule/index.tsx b/src/page/Project/Schedule/index.tsx new file mode 100644 index 000000000..82cee58b9 --- /dev/null +++ b/src/page/Project/Schedule/index.tsx @@ -0,0 +1,10 @@ +import ScheduleManage from '@/component/Schedule'; +import { SchedulePageMode } from '@/component/Schedule/interface'; + +interface IProps { + id: string; +} +const Schedule: React.FC = ({ id }) => { + return ; +}; +export default Schedule; diff --git a/src/page/Project/Task/index.tsx b/src/page/Project/Task/index.tsx index d43dabc15..cd7207c83 100644 --- a/src/page/Project/Task/index.tsx +++ b/src/page/Project/Task/index.tsx @@ -15,6 +15,7 @@ */ import TaskManage from '@/component/Task'; +import { TaskPageMode } from '@/component/Task/interface'; import tracert from '@/util/tracert'; import React, { useEffect } from 'react'; interface IProps { @@ -24,7 +25,7 @@ const Task: React.FC = (props) => { useEffect(() => { tracert.expo('a3112.b64002.c330859'); }, []); - return ; + return ; }; export default Task; diff --git a/src/page/Project/index.tsx b/src/page/Project/index.tsx index 1493e0fb9..4b0872962 100644 --- a/src/page/Project/index.tsx +++ b/src/page/Project/index.tsx @@ -39,6 +39,7 @@ import { ProjectTabType } from '@/d.ts/project'; import Setting from './Setting'; import Task from './Task'; import User from './User'; +import Schedule from './Schedule'; import { getSessionStorageKey } from './helper'; import { observer, inject } from 'mobx-react'; import { UserStore } from '@/store/login'; @@ -102,6 +103,9 @@ const Pages = { [IPageType.Project_Notification]: { component: Notification, }, + [IPageType.Project_Schedule]: { + component: Schedule, + }, }; const tabs = [ { @@ -120,6 +124,10 @@ const tabs = [ //工单 key: IPageType.Project_Task, }, + { + tab: '作业', + key: IPageType.Project_Schedule, + }, { tab: formatMessage({ id: 'odc.page.Project.Member', @@ -280,6 +288,7 @@ const Index: React.FC = function (props) { [ProjectRole.DBA]: [ IPageType.Project_Database, IPageType.Project_Task, + IPageType.Project_Schedule, IPageType.Project_Sensitive, IPageType.Project_User, ], @@ -287,12 +296,14 @@ const Index: React.FC = function (props) { [ProjectRole.DEVELOPER]: [ IPageType.Project_Database, IPageType.Project_Task, + IPageType.Project_Schedule, IPageType.Project_User, ], [ProjectRole.OWNER]: [ IPageType.Project_Database, IPageType.Project_Task, + IPageType.Project_Schedule, IPageType.Project_Sensitive, IPageType.Project_Setting, IPageType.Project_User, @@ -302,6 +313,7 @@ const Index: React.FC = function (props) { [ProjectRole.SECURITY_ADMINISTRATOR]: [ IPageType.Project_Database, IPageType.Project_Task, + IPageType.Project_Schedule, IPageType.Project_Sensitive, IPageType.Project_User, ], @@ -309,6 +321,7 @@ const Index: React.FC = function (props) { [ProjectRole.PARTICIPANT]: [ IPageType.Project_Database, IPageType.Project_Task, + IPageType.Project_Schedule, IPageType.Project_User, ], }; diff --git a/src/page/Schedule/const.tsx b/src/page/Schedule/const.tsx new file mode 100644 index 000000000..717c6bcc2 --- /dev/null +++ b/src/page/Schedule/const.tsx @@ -0,0 +1,41 @@ +import { ScheduleType } from '@/d.ts/schedule'; +import { SchedulePageType } from '@/d.ts/schedule'; +import { formatMessage } from '@/util/intl'; +import settingStore from '@/store/setting'; +import { SchedulePageTextMap } from '@/constant/schedule'; + +export interface ITaskModeConfig { + pageType: SchedulePageType; + label: string; + enabled: () => boolean; +} +export type PartialTaskConfig = { [K in SchedulePageType]?: ITaskModeConfig }; +const schedlueConfig: PartialTaskConfig = { + [SchedulePageType.ALL]: { + label: SchedulePageTextMap[SchedulePageType.ALL], + pageType: SchedulePageType.ALL, + enabled: () => true, + }, + [SchedulePageType.DATA_ARCHIVE]: { + label: SchedulePageTextMap[SchedulePageType.DATA_ARCHIVE], + pageType: SchedulePageType.DATA_ARCHIVE, + enabled: () => settingStore.enableDataArchive, + }, + [SchedulePageType.DATA_DELETE]: { + label: SchedulePageTextMap[SchedulePageType.DATA_DELETE], + pageType: SchedulePageType.DATA_DELETE, + enabled: () => settingStore.enableDataClear, + }, + [SchedulePageType.SQL_PLAN]: { + label: SchedulePageTextMap[SchedulePageType.SQL_PLAN], + pageType: SchedulePageType.SQL_PLAN, + enabled: () => settingStore.enableSQLPlan, + }, + [SchedulePageType.PARTITION_PLAN]: { + label: SchedulePageTextMap[SchedulePageType.PARTITION_PLAN], + pageType: SchedulePageType.PARTITION_PLAN, + enabled: () => settingStore.enablePartitionPlan, + }, +}; + +export { schedlueConfig }; diff --git a/src/page/Schedule/index.tsx b/src/page/Schedule/index.tsx new file mode 100644 index 000000000..87e37aa04 --- /dev/null +++ b/src/page/Schedule/index.tsx @@ -0,0 +1,18 @@ +import PageContainer, { TitleType } from '@/component/PageContainer'; +import ScheduleManage from '@/component/Schedule'; + +const Schedule = () => { + return ( + + + + ); +}; + +export default Schedule; diff --git a/src/page/ScheduleCreatePage/index.tsx b/src/page/ScheduleCreatePage/index.tsx new file mode 100644 index 000000000..ed2d4377f --- /dev/null +++ b/src/page/ScheduleCreatePage/index.tsx @@ -0,0 +1,59 @@ +import CreatePage from '@/component/Schedule/modals/Create'; +import PageContainer, { TitleType } from '@/component/PageContainer'; +import useURLParams from '@/util/hooks/useUrlParams'; +import { ScheduleType } from '@/d.ts/schedule'; +import { useEffect, useMemo, useState } from 'react'; +import { SchedulePageTextMap } from '@/constant/schedule'; +import { LeftOutlined } from '@ant-design/icons'; +import { history, useLocation } from '@umijs/max'; +import { inject, observer } from 'mobx-react'; +import { ScheduleStore } from '@/store/schedule'; +import { SchedulePageMode } from '@/component/Schedule/interface'; + +interface IProps { + scheduleStore?: ScheduleStore; +} +const ScheduleCreatePage: React.FC = ({ scheduleStore }) => { + const { getParam } = useURLParams(); + const type = getParam('type') as ScheduleType; + const [title, setTitle] = useState(SchedulePageTextMap[ScheduleType.DATA_ARCHIVE]); + const isEdit = getParam('isEdit'); + const location = useLocation(); + + const mode = useMemo(() => { + return location.pathname.includes('project') + ? SchedulePageMode.PROJECT + : SchedulePageMode.COMMON; + }, [location.pathname]); + + useEffect(() => { + if (type && Object.values(ScheduleType).includes(type)) { + setTitle(SchedulePageTextMap[type]); + } + }, [type]); + + return ( + + { + history?.back(); + scheduleStore?.resetScheduleCreateData(); + }} + /> + {isEdit ? '编辑' + title + '作业' : '新建' + title + '作业'} + + ), + showDivider: true, + }} + > + + + ); +}; + +export default inject('scheduleStore')(observer(ScheduleCreatePage)); diff --git a/src/page/Secure/RiskLevel/components/Condition.tsx b/src/page/Secure/RiskLevel/components/Condition.tsx index c76d201a8..2a1508443 100644 --- a/src/page/Secure/RiskLevel/components/Condition.tsx +++ b/src/page/Secure/RiskLevel/components/Condition.tsx @@ -43,9 +43,11 @@ const Condition = ({ setShowConditionGroup, environmentMap, taskTypeIdMap, + scheduleTypeIdMap, sqlCheckResultIdMap, environmentOptions, taskTypeOptions, + scheduleTypeOptions, sqlCheckResultOptions, }) => { const [condition, setCondition] = useState( @@ -98,6 +100,11 @@ const Condition = ({ setValueOptions([]); return; } + case Expression.SCHEDULE_TYPE: { + setValueOptions(scheduleTypeOptions); + setValueMap(scheduleTypeIdMap); + return; + } default: { setValueOptions(environmentOptions); setValueMap(environmentMap); @@ -219,6 +226,10 @@ const Condition = ({ label: ExpressionMap[Expression.SQL_CHECK_RESULT], value: Expression.SQL_CHECK_RESULT, }, + { + label: ExpressionMap[Expression.SCHEDULE_TYPE], + value: Expression.SCHEDULE_TYPE, + }, ]} onSelect={(_, { value }) => { const data = formRef.getFieldsValue(); diff --git a/src/page/Secure/RiskLevel/components/InnerRiskLevel.tsx b/src/page/Secure/RiskLevel/components/InnerRiskLevel.tsx index 362c3540f..8d183228c 100644 --- a/src/page/Secure/RiskLevel/components/InnerRiskLevel.tsx +++ b/src/page/Secure/RiskLevel/components/InnerRiskLevel.tsx @@ -75,11 +75,15 @@ const InnerRiskLevel: React.FC = ({ currentRiskLevel, memor const [empty, setEmpty] = useState(true); const [environmentMap, setEnvironmentMap] = useState<{ [key in string | number]: string }>({}); const [taskTypeIdMap, setTaskTypeIdMap] = useState<{ [key in string | number]: string }>({}); + const [scheduleTypeIdMap, setScheduleTypeIdMap] = useState<{ [key in string | number]: string }>( + {}, + ); const [sqlCheckResultIdMap, setSqlCheckResultIdMap] = useState<{ [key in string | number]: string; }>({}); const [environmentOptions, setEnvironmentOptions] = useState([]); const [taskTypeOptions, setTaskTypeOptions] = useState([]); + const [scheduleTypeOptions, setScheduleTypeOptions] = useState([]); const [sqlCheckResultOptions, setSqlCheckResultOptions] = useState([]); const [showConditionGroup, setShowConditionGroup] = useState(false); const [isEdit, setIsEdit] = useState(false); @@ -154,6 +158,8 @@ const InnerRiskLevel: React.FC = ({ currentRiskLevel, memor setEnvironmentMap, setEnvironmentOptions, setTaskTypeIdMap, + setScheduleTypeIdMap, + setScheduleTypeOptions, setTaskTypeOptions, setSqlCheckResultIdMap, setSqlCheckResultOptions, @@ -394,9 +400,11 @@ const InnerRiskLevel: React.FC = ({ currentRiskLevel, memor setShowConditionGroup={setShowConditionGroup} environmentMap={taskTypeIdMap} taskTypeIdMap={taskTypeIdMap} + scheduleTypeIdMap={scheduleTypeIdMap} sqlCheckResultIdMap={sqlCheckResultIdMap} environmentOptions={environmentOptions} taskTypeOptions={taskTypeOptions} + scheduleTypeOptions={scheduleTypeOptions} sqlCheckResultOptions={sqlCheckResultOptions} /> ); @@ -446,9 +454,11 @@ const InnerRiskLevel: React.FC = ({ currentRiskLevel, memor setShowConditionGroup={setShowConditionGroup} environmentMap={taskTypeIdMap} taskTypeIdMap={taskTypeIdMap} + scheduleTypeIdMap={scheduleTypeIdMap} sqlCheckResultIdMap={sqlCheckResultIdMap} environmentOptions={environmentOptions} taskTypeOptions={taskTypeOptions} + scheduleTypeOptions={scheduleTypeOptions} sqlCheckResultOptions={sqlCheckResultOptions} /> ); @@ -535,6 +545,7 @@ const InnerRiskLevel: React.FC = ({ currentRiskLevel, memor rootNode={originRootNode} environmentMap={environmentMap} taskTypeIdMap={taskTypeIdMap} + scheduleTypeIdMap={scheduleTypeIdMap} sqlCheckResultIdMap={sqlCheckResultIdMap} showActionButton={() => { return ( diff --git a/src/page/Secure/RiskLevel/components/RootNodeContent.tsx b/src/page/Secure/RiskLevel/components/RootNodeContent.tsx index bb362a5aa..8a9ef8f70 100644 --- a/src/page/Secure/RiskLevel/components/RootNodeContent.tsx +++ b/src/page/Secure/RiskLevel/components/RootNodeContent.tsx @@ -25,6 +25,7 @@ const RootNodeContent = ({ rootNode, environmentMap, taskTypeIdMap, + scheduleTypeIdMap, sqlCheckResultIdMap, showActionButton, }) => { @@ -52,6 +53,10 @@ const RootNodeContent = ({ valueMap = {}; break; } + case Expression.SCHEDULE_TYPE: { + valueMap = scheduleTypeIdMap; + break; + } default: { valueMap = {}; break; diff --git a/src/page/Secure/RiskLevel/components/options.ts b/src/page/Secure/RiskLevel/components/options.ts index 3483a402e..1f7e6e2fb 100644 --- a/src/page/Secure/RiskLevel/components/options.ts +++ b/src/page/Secure/RiskLevel/components/options.ts @@ -19,6 +19,8 @@ import { listEnvironments } from '@/common/network/env'; import { TaskTypeMap } from '@/component/Task/component/TaskTable/const'; import { TaskType } from '@/d.ts'; import { RiskLevelEnum, RiskLevelTextMap } from '../../interface'; +import { ScheduleType } from '@/d.ts/schedule'; +import { ScheduleTextMap } from '@/constant/schedule'; export const getEnvironmentOptions = async () => { const rawData = (await listEnvironments()) || []; const newEnvOptions = rawData?.map((rd) => { @@ -49,8 +51,8 @@ export const getTaskTypeOptions = () => { value: TaskType.ASYNC, }, { - label: TaskTypeMap[TaskType.PARTITION_PLAN], - value: TaskType.PARTITION_PLAN, + label: ScheduleTextMap[ScheduleType.PARTITION_PLAN], + value: ScheduleType.PARTITION_PLAN, }, { label: formatMessage({ @@ -91,6 +93,29 @@ export const getTaskTypeOptions = () => { return newTaskTypeOptions; }; + +const getScheduleTypeOptions = () => { + const scheduleTypeOptions = [ + { + label: ScheduleTextMap[ScheduleType.PARTITION_PLAN], + value: ScheduleType.PARTITION_PLAN, + }, + { + label: ScheduleTextMap[ScheduleType.DATA_ARCHIVE], + value: ScheduleType.DATA_ARCHIVE, + }, + { + label: ScheduleTextMap[ScheduleType.DATA_DELETE], + value: ScheduleType.DATA_DELETE, + }, + { + label: ScheduleTextMap[ScheduleType.SQL_PLAN], + value: ScheduleType.SQL_PLAN, + }, + ]; + return scheduleTypeOptions; +}; + export const getSqlCheckResultOptions = () => { const sqlCheckResultOptions = [ { @@ -113,7 +138,9 @@ export const initOptions = async ({ setEnvironmentMap, setEnvironmentOptions, setTaskTypeIdMap, + setScheduleTypeIdMap, setTaskTypeOptions, + setScheduleTypeOptions, setSqlCheckResultIdMap, setSqlCheckResultOptions, }) => { @@ -126,10 +153,15 @@ export const initOptions = async ({ setEnvironmentMap(envMap); setEnvironmentOptions(envOptions); const taskTypeOptions = await getTaskTypeOptions(); + const scheduleTypeOptions = getScheduleTypeOptions(); const taskTypeIdMap = {}; + const scheduleTypeIdMap = {}; taskTypeOptions?.forEach(({ label, value }) => (taskTypeIdMap[value] = label)); + scheduleTypeOptions?.forEach(({ label, value }) => (scheduleTypeIdMap[value] = label)); + setScheduleTypeIdMap(scheduleTypeIdMap); setTaskTypeIdMap(taskTypeIdMap); setTaskTypeOptions(taskTypeOptions); + setScheduleTypeOptions(scheduleTypeOptions); const sqlCheckResultOptions = await getSqlCheckResultOptions(); const sqlChekcResultMap = {}; sqlCheckResultOptions?.forEach(({ label, value }) => (sqlChekcResultMap['' + value] = label)); diff --git a/src/page/Secure/RiskLevel/interface.ts b/src/page/Secure/RiskLevel/interface.ts index 1c4d53264..f7582e1b6 100644 --- a/src/page/Secure/RiskLevel/interface.ts +++ b/src/page/Secure/RiskLevel/interface.ts @@ -47,6 +47,7 @@ export enum Expression { SQL_CHECK_RESULT = 'SQL_CHECK_RESULT', PROJECT_NAME = 'PROJECT_NAME', DATABASE_NAME = 'DATABASE_NAME', + SCHEDULE_TYPE = 'SCHEDULE_TYPE', } export const ExpressionMap = { [Expression.ENVIRONMENT_NAME]: formatMessage({ @@ -69,6 +70,7 @@ export const ExpressionMap = { id: 'odc.src.page.Secure.RiskLevel.NameDatabase', defaultMessage: '数据库名称', }), //'数据库名称' + [Expression.SCHEDULE_TYPE]: '作业类型', }; export enum EOperator { EQUALS = 'EQUALS', diff --git a/src/page/Task/index.tsx b/src/page/Task/index.tsx index 8ba5da0a2..b719ab6be 100644 --- a/src/page/Task/index.tsx +++ b/src/page/Task/index.tsx @@ -17,6 +17,7 @@ import PageContainer, { TitleType } from '@/component/PageContainer'; import TaskManage from '@/component/Task'; import { formatMessage } from '@/util/intl'; +import { TaskPageMode } from '@/component/Task/interface'; const Task = () => { return ( @@ -27,7 +28,7 @@ const Task = () => { showDivider: true, }} > - + ); }; diff --git a/src/page/Workspace/ActivityBar/ index.tsx b/src/page/Workspace/ActivityBar/ index.tsx index 52bcd051c..f6c13b229 100644 --- a/src/page/Workspace/ActivityBar/ index.tsx +++ b/src/page/Workspace/ActivityBar/ index.tsx @@ -20,12 +20,13 @@ import MenuItem from '@/layout/SpaceContainer/Sider/MenuItem'; import MineItem from '@/layout/SpaceContainer/Sider/MineItem'; import SettingItem from '@/layout/SpaceContainer/Sider/SettingItem'; import SpaceSelect from '@/layout/SpaceContainer/Sider/SpaceSelect'; -import { openTasksPage } from '@/store/helper/page'; +import { openTasksPage, openSchedulesPage } from '@/store/helper/page'; import { UserStore } from '@/store/login'; import { ReactComponent as DBSvg } from '@/svgr/database_outline.svg'; import { ReactComponent as TaskSvg } from '@/svgr/icon_task.svg'; import { ReactComponent as ManagerSvg } from '@/svgr/operate.svg'; import { ReactComponent as CodeSvg } from '@/svgr/Snippet.svg'; +import { ReactComponent as ScheduleSvg } from '@/svgr/icon_schedule.svg'; import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; import { BulbOutlined, UserOutlined } from '@ant-design/icons'; @@ -37,6 +38,7 @@ import ActivityBarButton from './ActivityBarButton'; import styles from './index.less'; import Logo from './Logo'; import { ActivityBarItemType, ActivityBarItemTypeText } from './type'; +import { getFirstEnabledSchedule } from '@/component/Schedule/helper'; interface IProps { userStore?: UserStore; @@ -72,6 +74,12 @@ const ActivityBar: React.FC = (props) => { icon: TaskSvg, isVisible: true, }, + { + title: ActivityBarItemTypeText[ActivityBarItemType.Schedule], + key: ActivityBarItemType.Schedule, + icon: ScheduleSvg, + isVisible: true, + }, { title: ActivityBarItemTypeText[ActivityBarItemType.Manager], key: ActivityBarItemType.Manager, @@ -109,7 +117,13 @@ const ActivityBar: React.FC = (props) => { if (item.key === ActivityBarItemType.Task) { const firstEnabledTask = getFirstEnabledTask(); if (firstEnabledTask) { - openTasksPage(firstEnabledTask?.value); + openTasksPage(firstEnabledTask?.pageType); + } + } + if (item.key === ActivityBarItemType.Schedule) { + const firstEnabledSchedule = getFirstEnabledSchedule(); + if (firstEnabledSchedule) { + openSchedulesPage(firstEnabledSchedule?.pageType); } } context?.setActiveKey(item.key); diff --git a/src/page/Workspace/ActivityBar/type.ts b/src/page/Workspace/ActivityBar/type.ts index 7f45e78c5..9d3aae053 100644 --- a/src/page/Workspace/ActivityBar/type.ts +++ b/src/page/Workspace/ActivityBar/type.ts @@ -21,6 +21,7 @@ export enum ActivityBarItemType { Task = 'Task', Manager = 'manager', Page = 'project', + Schedule = 'Schedule', } export const ActivityBarItemTypeText = { [ActivityBarItemType.Database]: formatMessage({ @@ -32,6 +33,7 @@ export const ActivityBarItemTypeText = { id: 'odc.src.page.Workspace.ActivityBar.WorkOrder', defaultMessage: '工单', }), //'工单' + [ActivityBarItemType.Schedule]: '作业', [ActivityBarItemType.Script]: formatMessage({ id: 'odc.Workspace.ActivityBar.type.Script', defaultMessage: '脚本', diff --git a/src/page/Workspace/GlobalModals/index.tsx b/src/page/Workspace/GlobalModals/index.tsx index 4d5abd5bc..2fadca8d7 100644 --- a/src/page/Workspace/GlobalModals/index.tsx +++ b/src/page/Workspace/GlobalModals/index.tsx @@ -42,7 +42,6 @@ const GlobalModals: React.FC = function ({ modalStore }) { - diff --git a/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/config/database.tsx b/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/config/database.tsx index 4dd9bd518..70e10920e 100644 --- a/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/config/database.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/config/database.tsx @@ -21,6 +21,7 @@ import { DatabasePermissionType, DBObjectSyncStatus, IDatabase } from '@/d.ts/da import { openNewDefaultPLPage, openNewSQLPage, openOBClientPage } from '@/store/helper/page'; import { default as login, default as userStore } from '@/store/login'; import modal from '@/store/modal'; +import scheduleStore from '@/store/schedule'; import setting from '@/store/setting'; import { isLogicalDatabase } from '@/util/database'; import { isClient } from '@/util/env'; @@ -31,6 +32,8 @@ import { LoadingOutlined } from '@ant-design/icons'; import { message, Tooltip, Typography } from 'antd'; import { ResourceNodeType } from '../../type'; import { IMenuItemConfig } from '../type'; +import { ScheduleType } from '@/d.ts/schedule'; +import { SchedulePageMode } from '@/component/Schedule/interface'; const { Text } = Typography; @@ -447,12 +450,12 @@ export const databaseMenusConfig: Partial = () => { + useEffect(() => { + tracert.expo('a3112.b41896.c330990'); + }, []); + return ( + ; + }, + }, + ]} + /> + ); +}; + +export default Schedule; diff --git a/src/page/Workspace/SideBar/Task/index.tsx b/src/page/Workspace/SideBar/Task/index.tsx index 5309ba568..a92cb6ac3 100644 --- a/src/page/Workspace/SideBar/Task/index.tsx +++ b/src/page/Workspace/SideBar/Task/index.tsx @@ -14,12 +14,13 @@ * limitations under the License. */ -import Sider from '@/component/Task/container/Sider'; +import Sider from '@/component/Task/layout/Sider'; import { formatMessage } from '@/util/intl'; import tracert from '@/util/tracert'; import React, { useEffect } from 'react'; import SideTabs from '../components/SideTabs'; import styles from './index.less'; +import { TaskPageMode } from '@/component/Task/interface'; interface IProps {} @@ -36,7 +37,7 @@ const Task: React.FC = () => { key: 'task', actions: [], render() { - return ; + return ; }, }, ]} diff --git a/src/page/Workspace/SideBar/index.tsx b/src/page/Workspace/SideBar/index.tsx index 2ed870ef0..c68ea0379 100644 --- a/src/page/Workspace/SideBar/index.tsx +++ b/src/page/Workspace/SideBar/index.tsx @@ -24,6 +24,7 @@ import Manager from './Manager'; import ResourceTree from './ResourceTree/Container'; import Script from './Script'; import Task from './Task'; +import Schedule from './Schedule'; interface IProps {} @@ -32,6 +33,7 @@ const items = { [ActivityBarItemType.Script]: Script, [ActivityBarItemType.Task]: Task, [ActivityBarItemType.Manager]: Manager, + [ActivityBarItemType.Schedule]: Schedule, }; const SideBar: React.FC = function () { diff --git a/src/page/Workspace/components/CreateSchedule/index.less b/src/page/Workspace/components/CreateSchedule/index.less new file mode 100644 index 000000000..f65c22a38 --- /dev/null +++ b/src/page/Workspace/components/CreateSchedule/index.less @@ -0,0 +1,4 @@ +.CreatePageContainer { + padding: 12px 0px 12px 24px; + height: 100%; +} diff --git a/src/page/Workspace/components/CreateSchedule/index.tsx b/src/page/Workspace/components/CreateSchedule/index.tsx new file mode 100644 index 000000000..087dec33c --- /dev/null +++ b/src/page/Workspace/components/CreateSchedule/index.tsx @@ -0,0 +1,31 @@ +import { SchedulePageTextMap } from '@/constant/schedule'; +import { SchedulePageType } from '@/d.ts/schedule'; +import CreatePage from '@/component/Schedule/modals/Create'; +import { CreateSchedulePage } from '@/store/helper/page/pages/create'; +import { ModalStore } from '@/store/modal'; +import { SessionManagerStore } from '@/store/sessionManager'; +import styles from './index.less'; +import { SchedulePageMode } from '@/component/Schedule/interface'; + +export const getTitleByParams = (params: { scheduleType: SchedulePageType }) => { + const { scheduleType } = params; + let title = `新建${SchedulePageTextMap[scheduleType]}`; + return title; +}; + +interface IProps { + pageKey: string; + sessionManagerStore?: SessionManagerStore; + modalStore?: ModalStore; + params: CreateSchedulePage['pageParams']; +} +const CreateSchedule: React.FC = (props) => { + const { params } = props; + return ( +

+ +
+ ); +}; + +export default CreateSchedule; diff --git a/src/page/Workspace/components/SchedulePage/index.less b/src/page/Workspace/components/SchedulePage/index.less new file mode 100644 index 000000000..889f59655 --- /dev/null +++ b/src/page/Workspace/components/SchedulePage/index.less @@ -0,0 +1,4 @@ +.schedule { + height: 100%; + padding: 4px 12px 12px; +} diff --git a/src/page/Workspace/components/SchedulePage/index.tsx b/src/page/Workspace/components/SchedulePage/index.tsx new file mode 100644 index 000000000..e93369718 --- /dev/null +++ b/src/page/Workspace/components/SchedulePage/index.tsx @@ -0,0 +1,35 @@ +import { SchedulePageType } from '@/d.ts/schedule'; +import { SchedulePageTextMap } from '@/constant/schedule'; +import styles from './index.less'; +import Content from '@/component/Schedule/layout/Content'; +import { SchedulePageMode } from '@/component/Schedule/interface'; + +export const getScheduleTitleByParams = (params: { type: SchedulePageType }) => { + const { type } = params; + let title = ''; + switch (type) { + case SchedulePageType.ALL: { + title = '作业-所有作业'; + break; + } + default: { + title = `作业-${SchedulePageTextMap[type]}`; + break; + } + } + return title; +}; + +interface IProps { + pageKey: SchedulePageType; +} + +const SchedulePage: React.FC = ({ pageKey }) => { + return ( +
+ +
+ ); +}; + +export default SchedulePage; diff --git a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx index 3cdd23390..b12849b1b 100644 --- a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx +++ b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx @@ -31,11 +31,13 @@ import { DEFALT_WIDTH } from './const'; import { IDatabase } from '@/d.ts/database'; import styles from './index.less'; import SessionDropdown, { ISessionDropdownFiltersProps } from './SessionDropdown'; +import { ScheduleType } from '@/d.ts/schedule'; import { isNumber } from 'lodash'; interface IProps { value?: number; taskType?: TaskType; + scheduleType?: ScheduleType; width?: number | string; projectId?: number; dataSourceId?: number; @@ -46,11 +48,13 @@ interface IProps { datasourceMode?: boolean; projectMode?: boolean; onChange?: (value: number, database?: IDatabase) => void; + onInit?: (database?: IDatabase) => void; } const SelectItem: React.FC = ({ value, taskType, + scheduleType, projectId, dataSourceId, filters = null, @@ -64,6 +68,7 @@ const SelectItem: React.FC = ({ isLogicalDatabase = false, datasourceMode = false, projectMode = isLogicalDatabase, + onInit, }) => { const { data: database, run: runDatabase } = useRequest(getDatabase, { manual: true, @@ -76,18 +81,24 @@ const SelectItem: React.FC = ({ const { data: logicalDatabase, run: runLogicalDatabase } = useRequest(logicalDatabaseDetail, { manual: true, }); - useEffect(() => { - if (value) { - if (datasourceMode) { - runDataSource(value); + + const initDatabase = async () => { + if (datasourceMode) { + runDataSource(value); + } else { + if (isLogicalDatabase) { + runLogicalDatabase(value); } else { - if (isLogicalDatabase) { - runLogicalDatabase(value); - return; - } - runDatabase(value); + const res = await runDatabase(value); + onInit?.(res?.data); } } + }; + + useEffect(() => { + if (value) { + initDatabase(); + } }, [value]); const dbIcon = getDataSourceStyleByConnectType(database?.data?.dataSource?.type)?.dbIcon; @@ -190,6 +201,7 @@ const SelectItem: React.FC = ({ filters={filters} width={width || DEFALT_WIDTH} taskType={taskType} + scheduleType={scheduleType} disabled={disabled} > = (props) => { }} name={[name, 'format']} > - - (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) - } - /> - - - - - - -
open(index)} style={{ cursor: 'pointer' }}> - {form.getFieldValue(['tables', name, 'joinTableConfigs']) - ?.length ? ( - - ) : ( - - )} -
-
- - } - /> -
- {enabledTargetTable && ( -
- - - + + - {enablePartition && ( - - )} -
- )} + +
open(index)} style={{ cursor: 'pointer' }}> + {form.getFieldValue(['tables', name, 'joinTableConfigs']) + ?.length ? ( + + ) : ( + + )} +
+
+ + } + /> + + {enabledTargetTable && ( +
+ + + - {fields?.length > 1 && ( - remove(name)} style={{ textAlign: 'center' }}> - {formatMessage({ - id: 'src.component.Task.DataArchiveTask.CreateModal.890DB04E', - defaultMessage: '移除', - })} - - )} -
- ))} - -
- - handleConfirm(checkList, add, remove)} - /> -
-
- - )} + {enablePartition && ( + + )} + + )} + + {fields?.length > 1 && ( + + remove(name)} + style={{ margin: ' 0px 0px 10px 8px' }} + /> + + )} + + ))} + + + + + handleConfirm(checkList, add, remove)} + /> + + + + + ); + }} ); diff --git a/src/component/Schedule/modals/DataArchive/Create/VariableConfig.tsx b/src/component/Schedule/modals/DataArchive/Create/VariableConfig.tsx index 45e8b0193..3164fedd0 100644 --- a/src/component/Schedule/modals/DataArchive/Create/VariableConfig.tsx +++ b/src/component/Schedule/modals/DataArchive/Create/VariableConfig.tsx @@ -18,7 +18,7 @@ import HelpDoc from '@/component/helpDoc'; import { formatMessage } from '@/util/intl'; import { DeleteOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons'; import type { FormInstance } from 'antd'; -import { Button, Form, Input, InputNumber, Select, Space, Tooltip } from 'antd'; +import { AutoComplete, Button, Form, Input, InputNumber, Select, Space, Tooltip } from 'antd'; import classNames from 'classnames'; import { variable } from './index'; import styles from './index.less'; @@ -81,7 +81,7 @@ export const timeUnitOptions = [ value: 'y', }, ]; - +const MAX_VARIABLES_COUNT = 100; const ENABLE_PATTERN_OPERATOR = false; const timeFormatOptions = ['yyyy-MM-dd', 'yyyyMMdd'].map((item) => ({ label: item, @@ -96,6 +96,7 @@ interface IProps { } const VariableConfig: React.FC = (props) => { const variables = Form.useWatch('variables', props.form); + return ( = (props) => { )} - {(fields, { add, remove }) => ( -
- {fields.map(({ key, name, ...restField }, index) => ( -
- - - - - - - - - - - + + + + + + {(subFields, { add: _add, remove: _remove }) => { + const disabledAddSubFields = subFields.length >= 3; + const required = !!Object.values(variables?.[index].pattern?.[0] ?? {})?.join( + '', + )?.length; + return ( +
+ {subFields.map(({ key, name, ...restField }) => ( +
+ + + + {ENABLE_PATTERN_OPERATOR && ( + <> + + + + + + + + )} +
+ ))} +
+ ); + }} +
+ + remove(name)} /> + +
+ ))} + + + -
- ))} - - - - - )} + + + ); + }}
); diff --git a/src/component/Schedule/modals/DataArchive/Create/index.less b/src/component/Schedule/modals/DataArchive/Create/index.less index f1df1e059..941e492a5 100644 --- a/src/component/Schedule/modals/DataArchive/Create/index.less +++ b/src/component/Schedule/modals/DataArchive/Create/index.less @@ -17,6 +17,12 @@ } } } + .mb8 { + margin-bottom: 8px; + } + .pr6 { + padding-right: 6px; + } .pattern { display: grid; grid-gap: 5px; @@ -38,17 +44,11 @@ grid-template-columns: 160px 1fr 1fr; align-items: baseline; &.delete { - grid-template-columns: 160px 1fr 1fr 35px; - } - &.title { - background-color: var(--neutral-grey2-color); - border: 1px solid var(---table-border-color); - border-bottom: 0; + grid-template-columns: 160px 1fr 1fr 25px; } } .tableTitle { padding: 3px 8px; - border-right: 1px solid var(---table-border-color); display: inline-flex; gap: 4px; } @@ -94,18 +94,15 @@ display: grid; grid-template-columns: 160px 1fr 1fr; width: 100%; - border: 1px solid var(---table-border-color); - border-bottom: 0; align-items: center; :global { .ant-form-item { - border-right: 1px solid var(---table-border-color); margin-bottom: 0; height: 100%; display: grid; place-items: center; .ant-form-item-row { - padding: 8px; + padding: 0px 0px 8px 0px; width: 100%; } } @@ -123,16 +120,4 @@ } } } - .operationContainer { - width: 100%; - display: flex; - justify-content: center; - border: 1px solid var(--odc-border-color); - button { - margin-right: 6px; - } - button:last-child { - margin-right: 0px; - } - } } diff --git a/src/component/Schedule/modals/DataArchive/Create/index.tsx b/src/component/Schedule/modals/DataArchive/Create/index.tsx index ee035504f..6229b394a 100644 --- a/src/component/Schedule/modals/DataArchive/Create/index.tsx +++ b/src/component/Schedule/modals/DataArchive/Create/index.tsx @@ -16,7 +16,6 @@ import { } from '@/d.ts'; import { createSchedule, updateSchedule, getScheduleDetail } from '@/common/network/schedule'; import { SchedulePageType, ScheduleType } from '@/d.ts/schedule'; -import { CreateScheduleContext } from '@/component/Schedule/context/createScheduleContext'; import { useDBSession } from '@/store/sessionManager/hooks'; import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; @@ -25,7 +24,7 @@ import { FieldTimeOutlined } from '@ant-design/icons'; import { Button, Checkbox, DatePicker, Form, Modal, Radio, Space, Spin, message } from 'antd'; import { inject, observer } from 'mobx-react'; import dayjs from 'dayjs'; -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import DatabaseSelect from '@/component/Task/component/DatabaseSelect'; import SQLPreviewModal from '@/component/Task/component/SQLPreviewModal'; import SynchronizationItem from '@/component/Task/component/SynchronizationItem'; @@ -48,6 +47,9 @@ import { IScheduleRecord, IDataArchiveParameters } from '@/d.ts/schedule'; import { PageStore } from '@/store/page'; import { SchedulePageMode } from '@/component/Schedule/interface'; import { openSchedulesPage } from '@/store/helper/page'; +import { getDataSourceModeConfig } from '@/common/datasource'; +import { ConnectTypeText } from '@/constant/label'; + export enum IArchiveRange { PORTION = 'portion', ALL = 'all', @@ -113,8 +115,8 @@ const defaultValue = { tables: [null], migrationInsertAction: MigrationInsertAction.INSERT_DUPLICATE_UPDATE, shardingStrategy: ShardingStrategy.AUTO, - rowLimit: 100, - dataSizeLimit: 1, + rowLimit: 1000, + dataSizeLimit: 10, }; interface IProps { @@ -132,6 +134,7 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) const [tables, setTables] = useState(); const [enablePartition, setEnablePartition] = useState(false); const [targetDatabase, setTargetDatabase] = useState(); + const [sourceDatabase, setSourceDatabase] = useState(); const [form] = Form.useForm(); const databaseId = Form.useWatch('databaseId', form); const { session: sourceDBSession, database: sourceDB } = useDBSession(databaseId); @@ -140,9 +143,6 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) const tables = await getTableListByDatabaseName(sourceDBSession?.sessionId, sourceDB?.name); setTables(tables); }; - const { createScheduleDatabase, setCreateScheduleDatabase } = - useContext(CreateScheduleContext) || {}; - const crontabRef = useRef<{ setValue: (value: ICrontab) => void; resetFields: () => void; @@ -151,10 +151,10 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) const dataArchiveEditId = dataArchiveData?.id; const isEdit = !!dataArchiveEditId && dataArchiveData?.type === 'EDIT'; const [isdeleteAfterMigration, setIsdeleteAfterMigration] = useState(false); + const [isTargetConnectTypeAllow, setIsTargetConnectTypeAllow] = useState(true); const loadEditData = async (editId: number) => { const dataRes = (await fetchScheduleDetail(editId)) as IScheduleRecord; - setCreateScheduleDatabase(dataRes?.parameters?.sourceDatabase); const { parameters, scheduleName, @@ -220,6 +220,8 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) } await form.setFieldsValue(formData); setTargetDatabase(parameters.targetDatabase); + setSourceDatabase(parameters.sourceDatabase); + handleCheckTargetConnectTypeIsAllow(parameters.sourceDatabase, parameters.targetDatabase); }; const handleCancel = async (hasEdit: boolean) => { if (hasEdit) { @@ -463,12 +465,37 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) setCrontab(null); setHasEdit(false); setTargetDatabase(null); - setCreateScheduleDatabase(undefined); + setSourceDatabase(null); }; - const handleDBChange = (v, db) => { + const handleSourceDatabaseChange = (v, db) => { form.setFieldValue('tables', [null]); - setCreateScheduleDatabase(db); + setSourceDatabase(db); + handleCheckTargetConnectTypeIsAllow(db, targetDatabase); + }; + + const handleTargetDatabaseChange = (v, db) => { + setTargetDatabase(db); + handleCheckTargetConnectTypeIsAllow(sourceDatabase, db); + }; + + const handleCheckTargetConnectTypeIsAllow = ( + sourceDatabase: IDatabase, + targetDatabase: IDatabase, + ) => { + if (!sourceDatabase?.dataSource?.type || !targetDatabase?.dataSource?.type) { + return; + } + const allowTargetConnectType = getDataSourceModeConfig(sourceDatabase?.dataSource?.type) + ?.features?.scheduleConfig?.allowTargetConnectTypeByDataArchive; + if ( + allowTargetConnectType && + allowTargetConnectType?.includes(targetDatabase?.dataSource?.type) + ) { + setIsTargetConnectTypeAllow(true); + } else { + setIsTargetConnectTypeAllow(false); + } }; useEffect(() => { @@ -558,8 +585,8 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) defaultMessage: '源端数据库', })} /*源端数据库*/ projectId={projectId} - onChange={handleDBChange} - onInit={(db) => setCreateScheduleDatabase(db)} + onChange={handleSourceDatabaseChange} + onInit={(db) => setTargetDatabase(db)} filters={{ hideFileSystem: true, }} @@ -571,10 +598,37 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) id: 'odc.DataArchiveTask.CreateModal.TargetDatabase', defaultMessage: '目标数据库', })} - onChange={(_, database) => { - setTargetDatabase(database); - }} - /*目标数据库*/ name="targetDataBaseId" + rules={[ + { + required: true, + message: formatMessage({ + id: 'odc.component.DatabaseSelect.SelectADatabase', + defaultMessage: '请选择数据库', + }), //请选择数据库 + }, + { + validator: (rule, value, callback) => { + if (!value) { + callback(); + return; + } + const sourceConnectTypeName = ConnectTypeText( + sourceDatabase?.dataSource?.type, + ); + const targetConnectTypeName = ConnectTypeText( + targetDatabase?.dataSource?.type, + ); + if (!isTargetConnectTypeAllow) { + callback( + `源端 ${sourceConnectTypeName} 类型 -> 目标端 ${targetConnectTypeName} 类型不在已支持的归档链路范围内`, + ); + } + callback(); + }, + }, + ]} + onChange={handleTargetDatabaseChange} + name="targetDataBaseId" projectId={projectId} /> @@ -743,7 +797,7 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) = (props) => {
1, [styles.advancedOption]: needCheckBeforeDelete || enablePartition, })} @@ -141,7 +142,7 @@ const ArchiveRange: React.FC = (props) => { })}
-
+
{ formatMessage({ id: 'odc.DataClearTask.CreateModal.ArchiveRange.CleaningConditions', @@ -189,128 +190,153 @@ const ArchiveRange: React.FC = (props) => { )}
- {(fields, { add, remove }) => ( -
- {fields.map(({ key, name, ...restField }: any, index) => ( -
1, - [styles.advancedOption]: hasAdvancedOptionCol, - })} - > - { + const disabledAddFields = fields.length >= MAX_TABLES_COUNT; + return ( +
+ {fields.map(({ key, name, ...restField }: any, index) => ( +
1, + [styles.advancedOption]: hasAdvancedOptionCol, + })} > - + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + /> + + - (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) - } - /> - - - - - - -
open(index)} style={{ cursor: 'pointer' }}> - {form.getFieldValue(['tables', name, 'joinTableConfigs']) - ?.length ? ( - - ) : ( - - )} -
-
- - } - /> -
- {(needCheckBeforeDelete || enablePartition) && ( -
- {needCheckBeforeDelete && ( - - - - )} + + - {enablePartition && ( - - )} -
- )} + +
open(index)} style={{ cursor: 'pointer' }}> + {form.getFieldValue(['tables', name, 'joinTableConfigs']) + ?.length ? ( + + ) : ( + + )} +
+
+ + } + /> + + {(needCheckBeforeDelete || enablePartition) && ( +
+ {needCheckBeforeDelete && ( + + + + )} - {fields?.length > 1 && ( - remove(name)} style={{ textAlign: 'center' }}> - {formatMessage({ - id: 'src.component.Task.DataClearTask.CreateModal.F0991266', - defaultMessage: '移除', - })} - - )} -
- ))} - -
- - handleConfirm(checkList, add, remove)} - /> -
-
-
- )} + {enablePartition && ( + + )} +
+ )} + + {fields?.length > 1 && ( + + remove(name)} + style={{ margin: '0px 0px 10px 8px' }} + /> + + )} +
+ ))} + + + + + handleConfirm(checkList, add, remove)} + /> + + + +
+ ); + }}
); diff --git a/src/component/Schedule/modals/DataClear/Create/VariableConfig.tsx b/src/component/Schedule/modals/DataClear/Create/VariableConfig.tsx index 41d9bc64a..3047cbd6a 100644 --- a/src/component/Schedule/modals/DataClear/Create/VariableConfig.tsx +++ b/src/component/Schedule/modals/DataClear/Create/VariableConfig.tsx @@ -18,7 +18,7 @@ import HelpDoc from '@/component/helpDoc'; import { formatMessage } from '@/util/intl'; import { DeleteOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons'; import type { FormInstance } from 'antd'; -import { Button, Form, Input, InputNumber, Select, Space } from 'antd'; +import { AutoComplete, Button, Form, Input, InputNumber, Select, Space, Tooltip } from 'antd'; import classNames from 'classnames'; import { timeUnitOptions } from '@/component/Schedule/modals/DataArchive/Create/VariableConfig'; import { variable } from './index'; @@ -37,6 +37,7 @@ interface IProps { form: FormInstance; } interface IProps {} +const MAX_VARIABLES_COUNT = 100; const VariableConfig: React.FC = (props) => { const variables = Form.useWatch('variables', props.form); return ( @@ -116,132 +117,145 @@ const VariableConfig: React.FC = (props) => { )} - {(fields, { add, remove }) => ( -
- {fields.map(({ key, name, ...restField }, index) => ( -
- - - - { + const disabledAddFields = fields.length >= MAX_VARIABLES_COUNT; + return ( +
+ {fields.map(({ key, name, ...restField }, index) => ( +
- - - - - - - + + + + + + {(subFields, { add: _add, remove: _remove }) => { + const disabledAddSubFields = subFields.length >= 3; + const required = !!Object.values( + variables?.[index]?.pattern?.[0] ?? {}, + )?.join('')?.length; + return ( +
+ {subFields.map(({ key, name, ...restField }) => ( +
+ + + + {ENABLE_PATTERN_OPERATOR && ( + <> + + + + )} +
+ ))} +
+ ); + }} +
+ remove(name)} /> +
+ ))} + + + + + +
+ ); + }} ); diff --git a/src/component/Schedule/modals/DataClear/Create/index.less b/src/component/Schedule/modals/DataClear/Create/index.less index 31328c695..16b03071a 100644 --- a/src/component/Schedule/modals/DataClear/Create/index.less +++ b/src/component/Schedule/modals/DataClear/Create/index.less @@ -33,45 +33,38 @@ .tables { display: grid; width: 100%; - border: 1px solid var(--table-border-color); - border-bottom: 0; align-items: center; width: 100%; grid-template-columns: 160px 1fr; &.delete { - grid-template-columns: 160px 1fr 35px; + grid-template-columns: 160px 1fr 25px; } &.advancedOption { grid-template-columns: 160px 186px 1fr; } &.delete.advancedOption { - grid-template-columns: 160px 186px 1fr 35px; + grid-template-columns: 160px 186px 1fr 25px; } - &.title { - background-color: var(--neutral-grey2-color); - border: 1px solid var(--table-border-color); - border-bottom: 0; - } :global { .ant-form-item { - border-right: 1px solid var(--table-border-color); margin-bottom: 0; height: 100%; display: grid; place-items: center; .ant-form-item-row { - padding: 8px; + padding: 0px 0px 8px 0px; width: 100%; } } } } .tableTitle { - // padding: 3px 8px; - border-right: 1px solid var(--table-border-color); + padding: 3px 8px; + } + .pr6 { + padding-right: 6px; } - .hide { display: none; } @@ -110,6 +103,9 @@ } .infoBlock { + .mb8 { + margin-bottom: 8px; + } .multiInputBox { > :global(.ant-form-item):not(:last-child) { > :global(.ant-form-item-row) { @@ -122,16 +118,4 @@ } } } - .operationContainer { - width: 100%; - display: flex; - justify-content: center; - border: 1px solid var(--odc-border-color); - button { - margin-right: 6px; - } - button:last-child { - margin-right: 0px; - } - } } diff --git a/src/component/Schedule/modals/DataClear/Create/index.tsx b/src/component/Schedule/modals/DataClear/Create/index.tsx index 383a17bdc..28e44b70e 100644 --- a/src/component/Schedule/modals/DataClear/Create/index.tsx +++ b/src/component/Schedule/modals/DataClear/Create/index.tsx @@ -77,8 +77,8 @@ const defaultValue = { archiveRange: IArchiveRange.PORTION, shardingStrategy: ShardingStrategy.AUTO, tables: [null], - rowLimit: 100, - dataSizeLimit: 1, + rowLimit: 1000, + dataSizeLimit: 10, deleteByUniqueKey: true, }; const getVariables = ( diff --git a/src/component/Task/component/DatabaseSelect/index.tsx b/src/component/Task/component/DatabaseSelect/index.tsx index 63dd51bd0..6f70a4045 100644 --- a/src/component/Task/component/DatabaseSelect/index.tsx +++ b/src/component/Task/component/DatabaseSelect/index.tsx @@ -22,6 +22,7 @@ import { IDatabase } from '@/d.ts/database'; import { Form } from 'antd'; import React from 'react'; import { ScheduleType } from '@/d.ts/schedule'; +import { Rule } from 'antd/lib/form'; interface IProps { type?: TaskType; @@ -38,6 +39,7 @@ interface IProps { isLogicalDatabase?: boolean; onChange?: (v: number, database?: IDatabase) => void; onInit?: (database?: IDatabase) => void; + rules?: Rule[]; } const DatabaseSelect: React.FC = (props) => { const { @@ -58,6 +60,7 @@ const DatabaseSelect: React.FC = (props) => { isLogicalDatabase = false, onChange, onInit, + rules, } = props; return ( @@ -65,15 +68,17 @@ const DatabaseSelect: React.FC = (props) => { label={label} name={name} required - rules={[ - { - required: true, - message: formatMessage({ - id: 'odc.component.DatabaseSelect.SelectADatabase', - defaultMessage: '请选择数据库', - }), //请选择数据库 - }, - ]} + rules={ + rules || [ + { + required: true, + message: formatMessage({ + id: 'odc.component.DatabaseSelect.SelectADatabase', + defaultMessage: '请选择数据库', + }), //请选择数据库 + }, + ] + } > Date: Mon, 18 Aug 2025 15:59:56 +0800 Subject: [PATCH 017/239] =?UTF-8?q?PullRequest:=20944=20fix:=20=E5=88=86?= =?UTF-8?q?=E5=8C=BA=E8=AE=A1=E5=88=92=E6=89=A7=E8=A1=8C=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=A2=AB=E7=BB=88=E6=AD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/04 of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/944 Reviewed-by: 晓康 * fix: 分区计划执行成功支持被终止 --- .../AsyncTaskOperationButton/helper.tsx | 23 ++++++++++++++----- .../hooks/useTaskTable.ts | 2 +- .../AsyncTaskOperationButton/index.tsx | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/component/Task/component/AsyncTaskOperationButton/helper.tsx b/src/component/Task/component/AsyncTaskOperationButton/helper.tsx index 7e820f0f4..22947462d 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/helper.tsx +++ b/src/component/Task/component/AsyncTaskOperationButton/helper.tsx @@ -6,7 +6,14 @@ import { } from '@/d.ts/migrateTask'; import { IAsyncTaskOperationConfig } from '.'; import { Popover, Space, Tooltip, Typography } from 'antd'; -import { IConnection, TaskDetail, TaskRecordParameters, TaskStatus, TaskType } from '@/d.ts'; +import { + IConnection, + TaskDetail, + TaskRecord, + TaskRecordParameters, + TaskStatus, + TaskType, +} from '@/d.ts'; import { TaskTypeMap } from '../TaskTable'; import { getLocalFormatDateTime } from '@/util/utils'; import { @@ -467,14 +474,18 @@ export const isScheduleMigrateTask = (taskType: TaskType) => { ]?.includes(taskType); }; -// 是否是在正常调度状态的任务(已创建, 已启用, 已禁用) -export const checkIsScheduleTaskListCanBeExported = (taskStatus: TaskStatus) => { - return statusThatCanBeExport?.includes(taskStatus); +// 是否是能被导出的任务状态 +export const checkIsScheduleTaskListCanBeExported = (task: TaskRecord) => { + return statusThatCanBeExport?.includes(task?.status); }; // 是否是能终止的任务状态 -export const checkIsTaskListCanBeTerminated = (taskStatus: TaskStatus) => { - return statusThatCanBeTerminate?.includes(taskStatus); +export const checkIsTaskListCanBeTerminated = (task: TaskRecord) => { + // 分区计划执行成功能够被终止 + if (task?.type === TaskType.PARTITION_PLAN && task?.status === TaskStatus.EXECUTION_SUCCEEDED) { + return true; + } + return statusThatCanBeTerminate?.includes(task?.status); }; /** diff --git a/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts b/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts index 67de61f97..810598fb4 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts +++ b/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts @@ -17,7 +17,7 @@ export interface AsyncTaskModalConfig { modalTitle: string; modalExtra: (count: number, ids?: number[]) => React.ReactNode; - checkStatus: (status: TaskStatus) => boolean; + checkStatus: (task: TaskRecord) => boolean; checkStatusFailed: string; onReload: () => void; diff --git a/src/component/Task/component/AsyncTaskOperationButton/index.tsx b/src/component/Task/component/AsyncTaskOperationButton/index.tsx index 81ff73427..8517aa923 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/index.tsx +++ b/src/component/Task/component/AsyncTaskOperationButton/index.tsx @@ -103,7 +103,7 @@ export function AsyncTaskOperationButton(props: IAsyncTaskOperationConfig) { ); return; } - if (props?.dataSource?.find((d) => !props?.checkStatus?.(d?.status))) { + if (props?.dataSource?.find((d) => !props?.checkStatus?.(d))) { message.info(props?.checkStatusFailed); return; } From fdad8cfd22ef60e9a5cd506126a1462202186546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Mon, 18 Aug 2025 16:31:19 +0800 Subject: [PATCH 018/239] =?UTF-8?q?PullRequest:=20945=20fix:=20=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E8=B0=83=E8=AF=95=E7=94=A8=E4=BE=8B=E6=95=B0=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/delTestCase of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/945 Reviewed-by: 晓康 * fix: 修正调试用例数值 --- .../Schedule/modals/DataArchive/Create/ArchiveRange.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/component/Schedule/modals/DataArchive/Create/ArchiveRange.tsx b/src/component/Schedule/modals/DataArchive/Create/ArchiveRange.tsx index b6372a5fa..85301db2f 100644 --- a/src/component/Schedule/modals/DataArchive/Create/ArchiveRange.tsx +++ b/src/component/Schedule/modals/DataArchive/Create/ArchiveRange.tsx @@ -30,7 +30,7 @@ import { IDatabase } from '@/d.ts/database'; import JoinTableConfigModal from '@/component/Task/component/JoinTableConfigsModal'; import useJoinTableConfig from '@/component/Task/component/JoinTableConfigsModal/useJoinTableConfig'; import { rules } from './const'; -const MAX_TABLES_COUNT = 3; +const MAX_TABLES_COUNT = 100; const { Text, Link } = Typography; interface IProps { From c7065ec6e6b3bea6a1560406ea78b2f1155ea543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Mon, 18 Aug 2025 17:40:41 +0800 Subject: [PATCH 019/239] =?UTF-8?q?PullRequest:=20947=20feat:=20=E8=A1=A8?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=B8=AD=E6=B7=BB=E5=8A=A0=E6=9D=A1=E6=95=B0?= =?UTF-8?q?=E4=B8=8A=E9=99=90=E6=8F=90=E7=A4=BA=E8=AF=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/sqlConfig of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/947 Reviewed-by: 晓康 * feat: 表数据中添加条数上限提示语 * fix: 最小输入为1 --- .../components/DDLResultSet/index.tsx | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/page/Workspace/components/DDLResultSet/index.tsx b/src/page/Workspace/components/DDLResultSet/index.tsx index 0d78646f4..3210da10f 100644 --- a/src/page/Workspace/components/DDLResultSet/index.tsx +++ b/src/page/Workspace/components/DDLResultSet/index.tsx @@ -229,7 +229,7 @@ const DDLResultSet: React.FC = function (props) { /** * 数据量限制 */ - const [limit, setLimit] = useState(1000); + const [limit, setLimit] = useState(session?.params?.queryLimit); /** * 表数据搜索 */ @@ -1232,16 +1232,23 @@ const DDLResultSet: React.FC = function (props) { { if (limit == '' || isNil(limit)) { - setLimit(0); + setLimit(1); } }} - onChange={(limit) => setLimit(limit || 0)} + onChange={(limit) => { + const maxQueryLimit = session?.params?.maxQueryLimit; + if (limit > maxQueryLimit) { + const tips = `${formatMessage({ + id: 'src.component.SQLConfig.5E06ED93', + defaultMessage: '不超过查询条数上限', + })} ${maxQueryLimit}`; + message.error(tips); + } + setLimit(limit || 1); + }} min={1} precision={0} - placeholder={formatMessage({ - id: 'workspace.window.sql.limit.placeholder', - defaultMessage: '1000', - })} + defaultValue={session?.params?.queryLimit} style={{ width: 70, marginLeft: 8, From 459926ed8d27185b7fb772add7017625a12001af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Mon, 18 Aug 2025 17:50:47 +0800 Subject: [PATCH 020/239] =?UTF-8?q?PullRequest:=20946=20feat:=20=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A=E6=A0=B7=E5=BC=8F=E8=B0=83=E6=95=B4=EF=BC=8C=E5=B1=8F?= =?UTF-8?q?=E8=94=BD=E5=AE=A2=E6=88=B7=E7=AB=AF=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E6=96=87=E6=A1=88=E4=BB=A5=E9=80=82=E9=85=8Doic=E6=96=87?= =?UTF-8?q?=E6=A1=88=E8=AF=86=E5=88=AB=E3=80=81=E8=A1=A5=E5=85=85=E9=81=97?= =?UTF-8?q?=E6=BC=8F=E7=9A=84=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/scheduleOptimize of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/946 Reviewed-by: 晓康 * feat: 优化个人空间的展示、修复文案格式适配oic识别 * feat: 作业样式优化,依赖类型补充,作业屏蔽客户端 * fix: rollback main server changes --- src/common/network/schedule.ts | 14 ++- src/component/AnchorContainer/index.less | 1 + src/component/AnchorContainer/index.tsx | 9 +- .../components/Actions/ScheduleActions.tsx | 15 ++- .../Actions/ScheduleTaskActions.tsx | 15 ++- .../ExcecuteDetail/SqlplanExcecuteDetail.tsx | 30 +++--- .../components/ExcecuteDetail/index.tsx | 32 +++--- .../components/OperationRecord/index.tsx | 19 ++-- .../components/ScheduleDetailModal/index.tsx | 2 +- .../components/ScheduleResult/index.tsx | 59 +++++++--- .../ScheduleTable/ScheduleNameColumns.tsx | 24 +++-- .../components/ScheduleTable/index.tsx | 30 ++++-- .../components/SubTaskDetailModal/index.tsx | 15 ++- src/component/Schedule/layout/Content.tsx | 11 +- .../Schedule/layout/ScheduleDetail.tsx | 3 +- src/component/Schedule/layout/Sider.tsx | 2 +- .../Schedule/layout/SubTaskDetail.tsx | 47 ++++++-- .../modals/DataArchive/Content/index.tsx | 20 +++- .../modals/DataArchive/Create/index.tsx | 5 +- .../modals/DataClear/Content/index.tsx | 22 ++-- .../modals/DataClear/Create/index.tsx | 5 +- .../modals/PartitionPlan/Content/index.tsx | 29 +++-- .../modals/PartitionPlan/Create/index.tsx | 5 +- .../Schedule/modals/SQLPlan/Content/index.tsx | 20 ++-- .../Schedule/modals/SQLPlan/Create/index.tsx | 5 +- .../component/DataTransferModal/index.tsx | 12 ++- .../component/TaskTable/TaskNameColumn.tsx | 25 +++-- .../Task/component/TaskTable/index.tsx | 4 +- src/component/Task/const.ts | 22 ---- src/component/Task/helper.tsx | 6 +- .../AlterDdlTask/DetailContent/index.tsx | 3 +- .../modals/AsyncTask/DetailContent/index.tsx | 10 +- .../DataMockerTask/DetailContent/index.tsx | 14 ++- src/component/Task/modals/DetailModals.tsx | 1 - .../DetailContent/index.tsx | 13 ++- .../MutipleAsyncTask/DetailContent/index.tsx | 9 +- .../DetailContent/index.tsx | 23 ++-- .../ShadowSyncTask/DetailContent/index.tsx | 9 +- src/d.ts/index.ts | 21 +--- src/d.ts/scheduleTask.ts | 101 +++++++++++++++++- src/page/Schedule/const.tsx | 3 +- src/page/ScheduleCreatePage/index.tsx | 1 + .../ActivityBar/{ index.tsx => index.tsx} | 3 +- .../components/CreateSchedule/index.less | 2 +- src/page/Workspace/index.tsx | 2 +- 45 files changed, 487 insertions(+), 236 deletions(-) rename src/page/Workspace/ActivityBar/{ index.tsx => index.tsx} (99%) diff --git a/src/common/network/schedule.ts b/src/common/network/schedule.ts index 822c514f0..af546c3f2 100644 --- a/src/common/network/schedule.ts +++ b/src/common/network/schedule.ts @@ -7,7 +7,11 @@ import { ScheduleStatus, } from '@/d.ts/schedule'; import request from '@/util/request'; -import { ScheduleTaskStatus } from '@/d.ts/scheduleTask'; +import { + IScheduleTaskExecutionDetail, + ScheduleTaskStatus, + SubTaskParameters, +} from '@/d.ts/scheduleTask'; import { Operation, IResponseData, @@ -242,7 +246,9 @@ export async function getDownloadUrl(scheduleId: number, taskId: number) { /** * 获取执行视角下的子任务列表 */ -export const getSubTaskList = async (params): Promise> => { +export const getSubTaskList = async ( + params, +): Promise>> => { const res = await request.get(`api/v2/schedule/tasks`, { params, }); @@ -256,7 +262,7 @@ export const listScheduleTasks = async (params: { scheduleId: number; size: number; page: number; -}): Promise> => { +}): Promise>> => { const { scheduleId, size, page } = params; const res = await request.get(`/api/v2/schedule/schedules/${scheduleId}/tasks`, { params: omit(params, 'scheduleId'), @@ -270,7 +276,7 @@ export const listScheduleTasks = async (params: { export const detailScheduleTask = async ( scheduleId: number, taskId: number, -): Promise => { +): Promise> => { const res = await request.get(`/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}`); return res?.data; }; diff --git a/src/component/AnchorContainer/index.less b/src/component/AnchorContainer/index.less index 25afec77c..bbd1a632e 100644 --- a/src/component/AnchorContainer/index.less +++ b/src/component/AnchorContainer/index.less @@ -14,5 +14,6 @@ right: 0; top: 0; align-self: flex-start; + max-width: 200px; } } diff --git a/src/component/AnchorContainer/index.tsx b/src/component/AnchorContainer/index.tsx index 27cc423e8..057644c4a 100644 --- a/src/component/AnchorContainer/index.tsx +++ b/src/component/AnchorContainer/index.tsx @@ -1,18 +1,21 @@ -import React, { useRef } from 'react'; +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 } = props; + const { items, containerWrapStyle = {} } = props; const scrollContainerRef = useRef(null); return (
-
{props.children}
+
+ {props.children} +
scrollContainerRef.current!} diff --git a/src/component/Schedule/components/Actions/ScheduleActions.tsx b/src/component/Schedule/components/Actions/ScheduleActions.tsx index 3d2947146..ecc9a451d 100644 --- a/src/component/Schedule/components/Actions/ScheduleActions.tsx +++ b/src/component/Schedule/components/Actions/ScheduleActions.tsx @@ -119,8 +119,9 @@ const ScheduleActions: React.FC = (props) => { const _handleStop = async () => { const { scheduleId } = schedule; + const scheduleTypeText = ScheduleTextMap[schedule?.type]; Modal.confirm({ - title: `确定要终止${ScheduleTextMap[schedule?.type]}吗`, + title: `确定要终止${scheduleTypeText}吗`, content: ( <>
@@ -152,8 +153,9 @@ const ScheduleActions: React.FC = (props) => { const _handleDisable = async () => { const { scheduleId } = schedule; + const scheduleTypeText = ScheduleTextMap[schedule?.type]; Modal.confirm({ - title: `确定要禁用此${ScheduleTextMap[schedule?.type]}吗`, + title: `确定要禁用此${scheduleTypeText}吗`, content: ( <>
@@ -199,8 +201,9 @@ const ScheduleActions: React.FC = (props) => { const _handleEnable = async () => { const { scheduleId } = schedule; + const scheduleTypeText = ScheduleTextMap[schedule?.type]; Modal.confirm({ - title: `确定要启用此${ScheduleTextMap[schedule?.type]}吗`, + title: `确定要启用此${scheduleTypeText}吗`, content: ( <>
@@ -321,8 +324,9 @@ const ScheduleActions: React.FC = (props) => { const _handleDelete = async () => { const { scheduleId } = schedule; + const scheduleTypeText = ScheduleTextMap[schedule?.type]; Modal.confirm({ - title: `确定要删除此${ScheduleTextMap[schedule?.type]}吗`, + title: `确定要删除此${scheduleTypeText}吗`, content: ( <>
@@ -357,8 +361,9 @@ const ScheduleActions: React.FC = (props) => { const _handleRevoke = async () => { const { approveInstanceId } = schedule; + const scheduleTypeText = ScheduleTextMap[schedule?.type]; Modal.confirm({ - title: `确定要撤销此${ScheduleTextMap[schedule?.type]}审批吗`, + title: `确定要撤销此${scheduleTypeText}审批吗`, content:
审批撤销后,作业将进入终止态
, cancelText: formatMessage({ id: 'odc.TaskManagePage.component.TaskTools.Cancel', diff --git a/src/component/Schedule/components/Actions/ScheduleTaskActions.tsx b/src/component/Schedule/components/Actions/ScheduleTaskActions.tsx index 43ac2c5e5..693c9dba5 100644 --- a/src/component/Schedule/components/Actions/ScheduleTaskActions.tsx +++ b/src/component/Schedule/components/Actions/ScheduleTaskActions.tsx @@ -1,4 +1,10 @@ -import { ScheduleTaskActionsEnum, ScheduleTaskStatus, SubTaskType } from '@/d.ts/scheduleTask'; +import { + IScheduleTaskExecutionDetail, + ScheduleTaskActionsEnum, + ScheduleTaskStatus, + SubTaskParameters, + SubTaskType, +} from '@/d.ts/scheduleTask'; import { ScheduleTaskActionsTextMap } from '@/constant/scheduleTask'; import { useEffect, useMemo, useState } from 'react'; import Action from '@/component/Action'; @@ -28,10 +34,13 @@ import { widthPermission } from '@/util/utils'; import { IOperationTypeRole } from '@/d.ts/schedule'; interface ScheduleActionsIProps { - subTask: scheduleTask; + subTask: scheduleTask; onReloadList?: () => void; isDetailModal?: boolean; - handleView: (task: scheduleTask, visible: boolean) => void; + handleView: ( + task: scheduleTask, + visible: boolean, + ) => void; scheduleId?: number; icon?: JSX.Element; } diff --git a/src/component/Schedule/components/ExcecuteDetail/SqlplanExcecuteDetail.tsx b/src/component/Schedule/components/ExcecuteDetail/SqlplanExcecuteDetail.tsx index b47a168d0..0d0c6bee7 100644 --- a/src/component/Schedule/components/ExcecuteDetail/SqlplanExcecuteDetail.tsx +++ b/src/component/Schedule/components/ExcecuteDetail/SqlplanExcecuteDetail.tsx @@ -1,19 +1,21 @@ -import { scheduleTask } from '@/d.ts/scheduleTask'; -import { Drawer, Table, Descriptions, Spin } from 'antd'; +import { + ISqlPlanParametersSubTaskParameters, + ISqlPlanSubTaskExecutionDetails, + scheduleTask, +} from '@/d.ts/scheduleTask'; +import { Descriptions } from 'antd'; import { ScheduleTextMap } from '@/constant/schedule'; -import { getFormatDateTime, milliSecondsToHour } from '@/util/utils'; -import { ScheduleTaskStatusTextMap } from '@/constant/scheduleTask'; +import { getFormatDateTime } from '@/util/utils'; import { SimpleTextItem } from '@/component/Task/component/SimpleTextItem'; import { SQLContent } from '@/component/SQLContent'; import { formatMessage } from '@/util/intl'; -import { TaskType } from '@/d.ts'; import { getDataSourceModeConfigByConnectionMode } from '@/common/datasource'; import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons'; import ScheduleTaskStatusLabel from '@/component/Schedule/components/ScheduleTaskStatusLabel'; import { ScheduleType } from '@/d.ts/schedule'; interface SqlplanExcecuteDetailProps { - subTask: scheduleTask; + subTask: scheduleTask; } const renderExecutionResult = (successCount: number, failCount: number) => { @@ -44,20 +46,16 @@ const renderExecutionResult = (successCount: number, failCount: number) => { const SqlplanExcecuteDetail: React.FC = ({ subTask }) => { const failedRecordsStr = - subTask?.executionDetails?.failedRecord && subTask?.executionDetails?.failedRecord.join('\n'); + subTask?.executionDetails?.failedRecord && subTask?.executionDetails?.failedRecord?.join('\n'); return ( -
{ - console.log(subTask); - }} - > +
- {ScheduleTextMap[subTask.type]} - + {ScheduleTextMap[subTask.type]} + {getFormatDateTime(subTask?.createTime)} - + @@ -85,7 +83,7 @@ const SqlplanExcecuteDetail: React.FC = ({ subTask } direction="column" /> - + {renderExecutionResult( subTask?.executionDetails?.succeedStatements || 0, subTask?.executionDetails?.failedStatements || 0, diff --git a/src/component/Schedule/components/ExcecuteDetail/index.tsx b/src/component/Schedule/components/ExcecuteDetail/index.tsx index 238f81a3b..9f1fd8a13 100644 --- a/src/component/Schedule/components/ExcecuteDetail/index.tsx +++ b/src/component/Schedule/components/ExcecuteDetail/index.tsx @@ -1,36 +1,45 @@ -import { scheduleTask } from '@/d.ts/scheduleTask'; +import { + IDataArchiveParametersSubTaskParameters, + IDataArchiveSubTaskExecutionDetails, + IDataClearParametersSubTaskParameters, + IDataDeleteSubTaskExecutionDetails, + scheduleTask, +} from '@/d.ts/scheduleTask'; import React, { useEffect, useState } from 'react'; import { getLocalFormatDateTime } from '@/util/utils'; import CommonTable from '@/component/CommonTable'; import type { ColumnsType } from 'antd/es/table'; -import { SubTaskStatus, ISubTaskTaskUnit, SubTaskType } from '@/d.ts'; +import { ISubTaskTaskUnit, SubTaskExecuteType } from '@/d.ts'; import { formatMessage } from '@/util/intl'; import StatusLabel from '@/component/Task/component/Status'; -import { Drawer, Table, Descriptions, Spin } from 'antd'; +import { Descriptions } from 'antd'; const SubTaskTypeMap = { - [SubTaskType.MIGRATE]: { + [SubTaskExecuteType.MIGRATE]: { label: formatMessage({ id: 'src.d.ts.CA81991C', defaultMessage: '归档' }), }, - [SubTaskType.CHECK]: { + [SubTaskExecuteType.CHECK]: { label: formatMessage({ id: 'src.d.ts.8977156C', defaultMessage: '数据检查' }), }, - [SubTaskType.DELETE]: { + [SubTaskExecuteType.DELETE]: { label: formatMessage({ id: 'src.d.ts.237F5711', defaultMessage: '数据清理' }), }, - [SubTaskType.QUICK_DELETE]: { + [SubTaskExecuteType.QUICK_DELETE]: { label: formatMessage({ id: 'src.d.ts.CD43F08A', defaultMessage: '数据清理' }), }, - [SubTaskType.DEIRECT_DELETE]: { + [SubTaskExecuteType.DEIRECT_DELETE]: { label: formatMessage({ id: 'src.d.ts.910D42B5', defaultMessage: '数据清理' }), }, - [SubTaskType.ROLLBACK]: { + [SubTaskExecuteType.ROLLBACK]: { label: formatMessage({ id: 'src.d.ts.DF449BBC', defaultMessage: '回滚' }), }, }; interface ExcecuteDetailProps { - subTask: scheduleTask; + subTask: scheduleTask< + IDataClearParametersSubTaskParameters | IDataArchiveParametersSubTaskParameters, + IDataArchiveSubTaskExecutionDetails | IDataDeleteSubTaskExecutionDetails + >; } const ExcecuteDetail: React.FC = (props) => { @@ -40,8 +49,7 @@ const ExcecuteDetail: React.FC = (props) => { const handleGetList = async () => { if (subTask?.executionDetails?.length) { - let list = JSON.parse(subTask?.executionDetails); - list = list?.map((i, index) => { + const list = subTask?.executionDetails?.map((i, index) => { return { ...i, endTime: getLocalFormatDateTime(i?.endTime), diff --git a/src/component/Schedule/components/OperationRecord/index.tsx b/src/component/Schedule/components/OperationRecord/index.tsx index 57bf88149..80130e36c 100644 --- a/src/component/Schedule/components/OperationRecord/index.tsx +++ b/src/component/Schedule/components/OperationRecord/index.tsx @@ -1,5 +1,3 @@ -import DisplayTable from '@/component/DisplayTable'; -import DetailModal from '@/component/Task/modals/DetailModals'; import { formatMessage } from '@/util/intl'; import { getFormatDateTime } from '@/util/utils'; import React, { useEffect, useRef, useState } from 'react'; @@ -11,8 +9,8 @@ import { } from '@/d.ts/schedule'; import PartitionPlanHeader from './components/PartitionPlanHeader'; import ScheduleTaskActions from '../Actions/ScheduleTaskActions'; -import { listScheduleTasks, detailScheduleTask } from '@/common/network/schedule'; -import { scheduleTask } from '@/d.ts/scheduleTask'; +import { listScheduleTasks } from '@/common/network/schedule'; +import { IScheduleTaskExecutionDetail, scheduleTask, SubTaskParameters } from '@/d.ts/scheduleTask'; import ScheduleTaskStatusLabel from '../ScheduleTaskStatusLabel'; import SubTaskDetailModal from '@/component/Schedule/layout/SubTaskDetail'; import ExecutionInfoContainer from '@/component/Schedule/components/ExecutionInfoContainer'; @@ -24,7 +22,10 @@ import { IResponseData } from '@/d.ts'; const getConnectionColumns = (params: { reloadList: () => void; - onOpenDetail: (task: scheduleTask, visible: boolean) => void; + onOpenDetail: ( + task: scheduleTask, + visible: boolean, + ) => void; schedule: IScheduleRecord; }) => { const { onOpenDetail, schedule, reloadList } = params; @@ -108,7 +109,8 @@ interface IProps { const OperationRecord: React.FC = (props) => { const { schedule } = props; - const [subTaskRes, setSubTaskRes] = useState>(); + const [subTaskRes, setSubTaskRes] = + useState>>(); const [detailId, setDetailId] = useState(null); const [detailVisible, setDetailVisible] = useState(false); const [parentId, setParentId] = useState(); @@ -125,7 +127,10 @@ const OperationRecord: React.FC = (props) => { }; }, 6000); - const handleDetailVisible = async (task: scheduleTask, visible: boolean = false) => { + const handleDetailVisible = async ( + task: scheduleTask, + visible: boolean = false, + ) => { setDetailId(task?.id); setParentId(Number(task?.jobName)); setDetailVisible(visible); diff --git a/src/component/Schedule/components/ScheduleDetailModal/index.tsx b/src/component/Schedule/components/ScheduleDetailModal/index.tsx index a541f9129..5698194ec 100644 --- a/src/component/Schedule/components/ScheduleDetailModal/index.tsx +++ b/src/component/Schedule/components/ScheduleDetailModal/index.tsx @@ -1,4 +1,4 @@ -import { Drawer, message, Radio, Spin, Tag } from 'antd'; +import { Drawer, Radio, Spin, Tag } from 'antd'; import styles from './index.less'; import { ScheduleDetailType } from '@/d.ts/schedule'; import OperationRecord from '../OperationRecord'; diff --git a/src/component/Schedule/components/ScheduleResult/index.tsx b/src/component/Schedule/components/ScheduleResult/index.tsx index 48c1b96f4..e0cfd8ef2 100644 --- a/src/component/Schedule/components/ScheduleResult/index.tsx +++ b/src/component/Schedule/components/ScheduleResult/index.tsx @@ -1,27 +1,60 @@ import React, { useEffect, useState, useMemo } from 'react'; import ExcecuteDetail from '@/component/Schedule/components/ExcecuteDetail'; import SqlplanExcecuteDetail from '@/component/Schedule/components/ExcecuteDetail/SqlplanExcecuteDetail'; -import { scheduleTask, SubTaskType } from '@/d.ts/scheduleTask'; +import { + IDataArchiveParametersSubTaskParameters, + IDataArchiveSubTaskExecutionDetails, + IDataClearParametersSubTaskParameters, + IDataDeleteSubTaskExecutionDetails, + IScheduleTaskExecutionDetail, + ISqlPlanParametersSubTaskParameters, + ISqlPlanSubTaskExecutionDetails, + scheduleTask, + SubTaskParameters, + SubTaskType, +} from '@/d.ts/scheduleTask'; interface ScheduleResultProps { - subTask: scheduleTask; + subTask: scheduleTask; } const ScheduleResult: React.FC = (props) => { const { subTask } = props; - - if ( - [ - SubTaskType.DATA_ARCHIVE, - SubTaskType.DATA_DELETE, - SubTaskType.DATA_ARCHIVE_ROLLBACK, - SubTaskType.DATA_ARCHIVE_DELETE, - ].includes(subTask.type) - ) { - return ; + let result = null; + switch (subTask.type) { + case SubTaskType.DATA_ARCHIVE: + case SubTaskType.DATA_DELETE: + case SubTaskType.DATA_ARCHIVE_ROLLBACK: + case SubTaskType.DATA_ARCHIVE_DELETE: { + result = ( + + } + /> + ); + break; + } + case SubTaskType.SQL_PLAN: + case SubTaskType.PARTITION_PLAN: { + result = ( + + } + /> + ); + break; + } } - return ; + return result; }; export default ScheduleResult; diff --git a/src/component/Schedule/components/ScheduleTable/ScheduleNameColumns.tsx b/src/component/Schedule/components/ScheduleTable/ScheduleNameColumns.tsx index 5b257bb13..5d04e3bda 100644 --- a/src/component/Schedule/components/ScheduleTable/ScheduleNameColumns.tsx +++ b/src/component/Schedule/components/ScheduleTable/ScheduleNameColumns.tsx @@ -3,12 +3,15 @@ import Icon from '@ant-design/icons'; import classNames from 'classnames'; import { Tooltip } from 'antd'; import { ReactComponent as UserSvg } from '@/svgr/user.svg'; +import login from '@/store/login'; import dayjs from 'dayjs'; +import { SchedulePageMode } from '@/component/Schedule/interface'; import styles from './index.less'; interface IProps { record: IScheduleRecord; delList: number[]; + mode?: SchedulePageMode; onDetailVisible: ( schedule: IScheduleRecord, visible: boolean, @@ -16,7 +19,7 @@ interface IProps { ) => void; } const ScheduleName: React.FC = (props) => { - const { record, delList, onDetailVisible } = props; + const { record, delList, onDetailVisible, mode } = props; return (
@@ -62,12 +65,19 @@ const ScheduleName: React.FC = (props) => { {record?.creator?.name}
- 创建于 {dayjs(record?.createTime).format('YYYY-MM-DD HH:mm:ss')}· -
- - {record?.project?.name} - -
+ 创建于 {dayjs(record?.createTime).format('YYYY-MM-DD HH:mm:ss')} + {login.isPrivateSpace() || mode === SchedulePageMode.PROJECT ? ( + '' + ) : ( + <> + · +
+ + {record?.project?.name} + +
+ + )}
); diff --git a/src/component/Schedule/components/ScheduleTable/index.tsx b/src/component/Schedule/components/ScheduleTable/index.tsx index 28a4ad4b3..bb698c574 100644 --- a/src/component/Schedule/components/ScheduleTable/index.tsx +++ b/src/component/Schedule/components/ScheduleTable/index.tsx @@ -45,7 +45,7 @@ import ScheduleTaskStatusLabel from '../ScheduleTaskStatusLabel'; import { IResponseData } from '@/d.ts'; import ScheduleTaskActions from '@/component/Schedule/components/Actions/ScheduleTaskActions'; import { SubTypeTextMap } from '@/constant/scheduleTask'; -import { scheduleTask } from '@/d.ts/scheduleTask'; +import { IScheduleTaskExecutionDetail, scheduleTask, SubTaskParameters } from '@/d.ts/scheduleTask'; import odc from '@/plugins/odc'; import ProjectContext from '@/page/Project/ProjectContext'; import { isProjectArchived } from '@/page/Project/helper'; @@ -64,7 +64,7 @@ interface IProps { scheduleStore?: ScheduleStore; pageStore?: PageStore; scheduleRes: IResponseData>; - scheduleTaskRes: IResponseData; + scheduleTaskRes: IResponseData>; scheduleTabType: SchedulePageType; getTaskList: ( args: IScheduleParam | ISubTaskParam, @@ -76,7 +76,10 @@ interface IProps { visible: boolean, detailType?: ScheduleDetailType, ) => void; - onSubTaskDetailVisible: (subTask: scheduleTask, visible: boolean) => void; + onSubTaskDetailVisible: ( + subTask: scheduleTask, + visible: boolean, + ) => void; onMenuClick?: (type: ScheduleType) => void; onReloadList: () => void; loading: boolean; @@ -255,6 +258,7 @@ const ScheduleTable: React.FC = (props) => { record={record as IScheduleRecord} delList={delList} onDetailVisible={onDetailVisible} + mode={mode} /> ); }, @@ -366,6 +370,7 @@ const ScheduleTable: React.FC = (props) => { display: 'flex', gap: '4px', maxWidth: '100%', + width: 'max-content', }} className={classNames(styles.tip, styles.hoverLink)} > @@ -377,13 +382,18 @@ const ScheduleTable: React.FC = (props) => { ); }, }, - { - dataIndex: 'project', - title: '项目', - ellipsis: true, - width: 200, - render: (project) => project?.name, - }, + ...(mode === SchedulePageMode.PROJECT || login.isPrivateSpace() + ? [] + : [ + { + dataIndex: 'project', + title: '项目', + ellipsis: true, + width: 200, + render: (project) => project?.name, + }, + ]), + { dataIndex: 'type', title: '类型', diff --git a/src/component/Schedule/components/SubTaskDetailModal/index.tsx b/src/component/Schedule/components/SubTaskDetailModal/index.tsx index e9e208667..2584afdc0 100644 --- a/src/component/Schedule/components/SubTaskDetailModal/index.tsx +++ b/src/component/Schedule/components/SubTaskDetailModal/index.tsx @@ -1,5 +1,10 @@ -import { scheduleTask, ScheduleTaskDetailType } from '@/d.ts/scheduleTask'; -import { Drawer, message, Radio, Spin } from 'antd'; +import { + IScheduleTaskExecutionDetail, + scheduleTask, + ScheduleTaskDetailType, + SubTaskParameters, +} from '@/d.ts/scheduleTask'; +import { Drawer, Radio, Spin } from 'antd'; import styles from '../ScheduleDetailModal/index.less'; import ScheduleTaskActions from '../Actions/ScheduleTaskActions'; import ScheduleTaskStatusLabel from '../ScheduleTaskStatusLabel'; @@ -12,7 +17,7 @@ interface TaskContentProps { taskContent?: React.ReactNode; isLoading: boolean; detailType: ScheduleTaskDetailType; - subTask: scheduleTask; + subTask: scheduleTask; log: ILog; result: ITaskResult; logType: CommonTaskLogType; @@ -75,7 +80,7 @@ interface ICommonSubTaskDetailModalProps { onDetailTypeChange: (type: ScheduleTaskDetailType) => void; enabledAction: boolean; onReloadList?: () => void; - subTask: scheduleTask; + subTask: scheduleTask; log: ILog; result: ITaskResult; logType: CommonTaskLogType; @@ -115,7 +120,7 @@ const SubTaskDetailModal: React.FC = (props) => { - props.onDetailTypeChange(e.target.value); + onDetailTypeChange?.(e.target.value); }} > diff --git a/src/component/Schedule/layout/Content.tsx b/src/component/Schedule/layout/Content.tsx index ee8f2a58d..58c7de926 100644 --- a/src/component/Schedule/layout/Content.tsx +++ b/src/component/Schedule/layout/Content.tsx @@ -11,7 +11,7 @@ import { inject, observer } from 'mobx-react'; import { ScheduleStore } from '@/store/schedule'; import { useSetState } from 'ahooks'; import styles from '@/component/Schedule/index.less'; -import DetailModals from './ScheduleDetail'; +import ScheduleDetail from './ScheduleDetail'; import type { ITableInstance } from '@/component/CommonTable/interface'; import ScheduleTable from '../components/ScheduleTable'; import SubTaskDetailModal from '@/component/Schedule/layout/SubTaskDetail'; @@ -38,7 +38,7 @@ import { getPreTime } from '@/util/utils'; import { schedlueConfig } from '@/page/Schedule/const'; import ApprovalModal from '@/component/Task/component/ApprovalModal'; import { message } from 'antd'; -import { scheduleTask } from '@/d.ts/scheduleTask'; +import { IScheduleTaskExecutionDetail, scheduleTask, SubTaskParameters } from '@/d.ts/scheduleTask'; import { IPagination } from '@/component/Schedule/interface'; import { getFirstEnabledSchedule } from '../helper'; @@ -118,7 +118,10 @@ const Content: React.FC = (props) => { }); }; - const handleSubTaskDetailVisible = (subTask: scheduleTask, visible: boolean = false) => { + const handleSubTaskDetailVisible = ( + subTask: scheduleTask, + visible: boolean = false, + ) => { setSubTaskState({ detailId: subTask?.id, detailVisible: visible, @@ -350,7 +353,7 @@ const Content: React.FC = (props) => { setPagination={setPagination} />
- = (props) => { const { visible, onClose, detailId, scheduleId } = props; - const [subTask, setSubTask] = useState(null); + const [subTask, setSubTask] = + useState>(null); const [detailType, setDetailType] = useState(ScheduleTaskDetailType.INFO); const [log, setLog] = useState(null); const [result, setResult] = useState(null); @@ -130,7 +145,12 @@ const SubTaskDetail: React.FC = (props) => { taskContent = ( } - subTask={subTask} + subTask={ + subTask as scheduleTask< + ISqlPlanParametersSubTaskParameters, + ISqlPlanSubTaskExecutionDetails + > + } /> ); break; @@ -138,7 +158,12 @@ const SubTaskDetail: React.FC = (props) => { taskContent = ( } - subTask={subTask} + subTask={ + subTask as scheduleTask< + IPartitionPlanSubTaskParameters, + IPartitionPlanSubTaskExecutionDetails + > + } /> ); break; @@ -148,7 +173,12 @@ const SubTaskDetail: React.FC = (props) => { taskContent = ( } - subTask={subTask} + subTask={ + subTask as scheduleTask< + IDataArchiveParametersSubTaskParameters, + IDataArchiveSubTaskExecutionDetails + > + } /> ); break; @@ -156,7 +186,12 @@ const SubTaskDetail: React.FC = (props) => { taskContent = ( } - subTask={subTask} + subTask={ + subTask as scheduleTask< + IDataClearParametersSubTaskParameters, + IDataDeleteSubTaskExecutionDetails + > + } /> ); break; diff --git a/src/component/Schedule/modals/DataArchive/Content/index.tsx b/src/component/Schedule/modals/DataArchive/Content/index.tsx index 5da03ccae..498978b8d 100644 --- a/src/component/Schedule/modals/DataArchive/Content/index.tsx +++ b/src/component/Schedule/modals/DataArchive/Content/index.tsx @@ -19,12 +19,20 @@ import { SyncTableStructureConfig } from '@/component/Task/const'; import ThrottleEditableCell from '@/component/Task/component/ThrottleEditableCell'; import setting from '@/store/setting'; import { updateLimiterConfig } from '@/common/network/schedule'; -import { IScheduleTaskRecord, scheduleTask } from '@/d.ts/scheduleTask'; +import { + IDataArchiveParametersSubTaskParameters, + IDataArchiveSubTaskExecutionDetails, + scheduleTask, +} from '@/d.ts/scheduleTask'; import { SubTypeTextMap } from '@/constant/scheduleTask'; import EllipsisText from '@/component/EllipsisText'; +import login from '@/store/login'; interface IProps { schedule: IScheduleRecord; - subTask?: scheduleTask; + subTask?: scheduleTask< + IDataArchiveParametersSubTaskParameters, + IDataArchiveSubTaskExecutionDetails + >; onReload?: () => void; } const DataArchiveScheduleContent: React.FC = (props) => { @@ -116,9 +124,11 @@ const DataArchiveScheduleContent: React.FC = (props) => { - - - + {!login.isPrivateSpace() && ( + + + + )} = ({ scheduleStore, projectId, pageStore, mode }) className={styles.dataArchive} > = ({ scheduleStore, projectId, pageStore, mode }) onClose={handleCloseSQLPreviewModal} onOk={(scheduleName) => handleConfirmTask(scheduleName)} /> -
+
- - + {!login.isPrivateSpace() && ( + + } + /> + + )} {hasFlow && ( ; onDetailVisible: (record: TaskRecord, visible: boolean) => void; + mode?: TaskPageMode; } + const TaskNameColumn = (props: IProps) => { - const { record, onDetailVisible } = props; + const { record, onDetailVisible, mode } = props; const roleNames = record?.creator?.roleNames?.join(' | '); return ( @@ -65,12 +69,19 @@ const TaskNameColumn = (props: IProps) => { {record?.creator?.name} - 创建于 {dayjs(record?.createTime).format('YYYY-MM-DD HH:mm:ss')}· -
- - {record?.project?.name} - -
+ 创建于 {dayjs(record?.createTime).format('YYYY-MM-DD HH:mm:ss')} + {login.isPrivateSpace() || mode === TaskPageMode.PROJECT ? ( + '' + ) : ( + <> + · +
+ + {record?.project?.name} + +
+ + )} ); diff --git a/src/component/Task/component/TaskTable/index.tsx b/src/component/Task/component/TaskTable/index.tsx index 844aa267b..0b97d27dc 100644 --- a/src/component/Task/component/TaskTable/index.tsx +++ b/src/component/Task/component/TaskTable/index.tsx @@ -208,7 +208,9 @@ const TaskTable: React.FC = (props) => { title: '工单', dataIndex: 'id', width: 500, - render: (id, record) => , + render: (id, record) => ( + + ), }, { ...(isAll diff --git a/src/component/Task/const.ts b/src/component/Task/const.ts index 1e6ed29da..31ecefec5 100644 --- a/src/component/Task/const.ts +++ b/src/component/Task/const.ts @@ -15,7 +15,6 @@ */ import { - SubTaskType, SyncTableStructureEnum, TaskErrorStrategy, TaskExecStrategy, @@ -83,27 +82,6 @@ export const SyncTableStructureOptions = [ }, ]; -export const SubTaskTypeMap = { - [SubTaskType.MIGRATE]: { - label: formatMessage({ id: 'src.d.ts.CA81991C', defaultMessage: '归档' }), - }, - [SubTaskType.CHECK]: { - label: formatMessage({ id: 'src.d.ts.8977156C', defaultMessage: '数据检查' }), - }, - [SubTaskType.DELETE]: { - label: formatMessage({ id: 'src.d.ts.237F5711', defaultMessage: '数据清理' }), - }, - [SubTaskType.QUICK_DELETE]: { - label: formatMessage({ id: 'src.d.ts.CD43F08A', defaultMessage: '数据清理' }), - }, - [SubTaskType.DEIRECT_DELETE]: { - label: formatMessage({ id: 'src.d.ts.910D42B5', defaultMessage: '数据清理' }), - }, - [SubTaskType.ROLLBACK]: { - label: formatMessage({ id: 'src.d.ts.DF449BBC', defaultMessage: '回滚' }), - }, -}; - export const OscMinRowLimit = 1; export const OscMaxRowLimit = 10000; export const OscMaxDataSizeLimit = 1000; diff --git a/src/component/Task/helper.tsx b/src/component/Task/helper.tsx index 7f293bfb6..d4d8d3aa3 100644 --- a/src/component/Task/helper.tsx +++ b/src/component/Task/helper.tsx @@ -14,13 +14,9 @@ * limitations under the License. */ -import { SubTaskType, TaskExecStrategy, TaskPageType, TaskType } from '@/d.ts'; +import { TaskExecStrategy, TaskType } from '@/d.ts'; import { DatabasePermissionType } from '@/d.ts/database'; -import login from '@/store/login'; -import settingStore from '@/store/setting'; -import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; -import { flatten } from 'lodash'; export { TaskTypeMap } from '@/component/Task/component/TaskTable/const'; import { TaskConfig, allTaskPageConfig } from '@/common/task'; import { ITaskParam, TaskTab } from './interface'; diff --git a/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx b/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx index 5e19d41c6..4483f599f 100644 --- a/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx +++ b/src/component/Task/modals/AlterDdlTask/DetailContent/index.tsx @@ -33,6 +33,7 @@ import { ProjectRole } from '@/d.ts/project'; import userStore from '@/store/login'; import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; import EllipsisText from '@/component/EllipsisText'; +import login from '@/store/login'; const { Text } = Typography; interface IDDLAlterParamters { @@ -215,7 +216,7 @@ export function getItems( ['数据库', } />], ['数据源', ], - ['项目', ], + !login.isPrivateSpace() ? ['项目', ] : null, hasFlow ? riskItem : null, ].filter(Boolean), }, diff --git a/src/component/Task/modals/AsyncTask/DetailContent/index.tsx b/src/component/Task/modals/AsyncTask/DetailContent/index.tsx index 5cc797d62..3c30f7eba 100644 --- a/src/component/Task/modals/AsyncTask/DetailContent/index.tsx +++ b/src/component/Task/modals/AsyncTask/DetailContent/index.tsx @@ -69,15 +69,17 @@ const AsyncTaskContent: React.FC = (props) => { 数据库变更 - + } needTooltip={false} /> - - {task?.project?.name || '-'} - + {!login.isPrivateSpace() && ( + + + + )} {hasFlow && ( , result: ITaskResult, hasFlow: boolean) { if (!task) { return []; @@ -211,11 +212,16 @@ export function getItems(task: TaskDetail, result: ITaskResult, defaultMessage: '模拟数据', }), ], - ['数据库', ], - + [ + '数据库', + } + needTooltip={false} + />, + ], ['数据源', ], - ['项目', ], - ], + !login.isPrivateSpace() ? ['项目', ] : null, + ].filter(Boolean), }, taskDetailItems, columnsItems, diff --git a/src/component/Task/modals/DetailModals.tsx b/src/component/Task/modals/DetailModals.tsx index 6aa04c8b0..6a62d32ae 100644 --- a/src/component/Task/modals/DetailModals.tsx +++ b/src/component/Task/modals/DetailModals.tsx @@ -38,7 +38,6 @@ import { ApplyDatabasePermissionTaskContent } from './ApplyDatabasePermission'; import { ApplyPermissionTaskContent } from './ApplyPermission'; import { ApplyTablePermissionTaskContent } from './ApplyTablePermission'; import { AsyncTaskContent } from './AsyncTask'; -import ApprovalModal from '../component/ApprovalModal'; import { getItems as getDataMockerItems } from './DataMockerTask'; import { LogicDatabaseAsyncTaskContent } from './LogicDatabaseAsyncTask'; import { MutipleAsyncTaskContent } from './MutipleAsyncTask'; diff --git a/src/component/Task/modals/LogicDatabaseAsyncTask/DetailContent/index.tsx b/src/component/Task/modals/LogicDatabaseAsyncTask/DetailContent/index.tsx index b5c5d20e8..13c3aed57 100644 --- a/src/component/Task/modals/LogicDatabaseAsyncTask/DetailContent/index.tsx +++ b/src/component/Task/modals/LogicDatabaseAsyncTask/DetailContent/index.tsx @@ -11,6 +11,7 @@ import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; import { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; import EllipsisText from '@/component/EllipsisText'; +import login from '@/store/login'; export const ErrorStrategy = { ABORT: formatMessage({ id: 'src.component.Task.LogicDatabaseAsyncTask.DetailContent.11ED2337', @@ -49,14 +50,16 @@ const LogicDatabaseAsyncTaskContent: React.FC = (props) => { defaultMessage: '数据库', })} > - } /> + } needTooltip={false} /> - {task?.database?.dataSource?.name || '-'} - - - + + {!login.isPrivateSpace() && ( + + + + )} = - - - + {!login.isPrivateSpace() && ( + + + + )} , result: ITaskResult, @@ -76,14 +77,19 @@ export const getItems = ( })} - + } + needTooltip={false} + /> - {task?.database?.dataSource?.name || '-'} - - - {task?.project?.name || '-'} + + {!login.isPrivateSpace() && ( + + + + )} {hasFlow && ( - + )} diff --git a/src/component/Task/modals/ShadowSyncTask/DetailContent/index.tsx b/src/component/Task/modals/ShadowSyncTask/DetailContent/index.tsx index 77df859e4..62689ddf3 100644 --- a/src/component/Task/modals/ShadowSyncTask/DetailContent/index.tsx +++ b/src/component/Task/modals/ShadowSyncTask/DetailContent/index.tsx @@ -30,6 +30,7 @@ import { useEffect, useState } from 'react'; import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; import { getTaskExecStrategyMap } from '@/component/Task/const'; import EllipsisText from '@/component/EllipsisText'; +import login from '@/store/login'; interface IShadowSyncParamters { errorStrategy: ErrorStrategy; connectionId: string; @@ -136,8 +137,10 @@ export function getItems( ], [ '数据库', - //所属数据库 - , + } + needTooltip={false} + />, ], [ @@ -147,7 +150,7 @@ export function getItems( }), //'所属数据源' , ], - ['项目', ], + !login.isPrivateSpace() ? ['项目', ] : null, hasFlow ? riskItem : null, [ diff --git a/src/d.ts/index.ts b/src/d.ts/index.ts index 8600b8ff1..4074a1e25 100644 --- a/src/d.ts/index.ts +++ b/src/d.ts/index.ts @@ -2157,14 +2157,6 @@ export enum TaskJobType { DATA_DELETE = 'DATA_DELETE', } -export enum SubTaskType { - DATA_ARCHIVE = 'DATA_ARCHIVE', - DATA_DELETE = 'DATA_DELETE', - DATA_ARCHIVE_ROLLBACK = 'DATA_ARCHIVE_ROLLBACK', - DATA_ARCHIVE_DELETE = 'DATA_ARCHIVE_DELETE', - ASYNC = 'ASYNC', -} - export enum TaskSubType { INSERT = 'INSERT', UPDATE = 'UPDATE', @@ -2721,18 +2713,7 @@ export interface ICycleTaskStatRecord { }[]; } -export interface ICycleTaskJobRecord { - createTime: number; - executionDetails: T; - fireTime: number; - id: number; - parameters: any; - status: SubTaskStatus; - type: TaskType.LOGICAL_DATABASE_CHANGE; - updateTime: number; -} - -export enum SubTaskType { +export enum SubTaskExecuteType { MIGRATE = 'MIGRATE', CHECK = 'CHECK', DELETE = 'DELETE', diff --git a/src/d.ts/scheduleTask.ts b/src/d.ts/scheduleTask.ts index 62628f075..15c25e268 100644 --- a/src/d.ts/scheduleTask.ts +++ b/src/d.ts/scheduleTask.ts @@ -1,6 +1,7 @@ import { ScheduleType } from './schedule'; import { IDatabase } from './database'; import { IProject, ProjectRole } from './project'; +import { SubTaskExecuteType } from '.'; export enum ScheduleTaskStatus { /** 待调度 */ @@ -49,21 +50,115 @@ export enum ScheduleTaskActionsEnum { RETRY = 'RETRY', } -export interface scheduleTask { +export type IScheduleTaskExecutionDetail = + | IDataArchiveSubTaskExecutionDetails + | IDataDeleteSubTaskExecutionDetails + | IPartitionPlanSubTaskExecutionDetails + | ISqlPlanSubTaskExecutionDetails; +export type IDataArchiveSubTaskExecutionDetails = { + endTime?: number; + processedRowCount?: number; + processedRowsPerSecond?: number; + readRowCount?: number; + readRowsPerSecond?: number; + startTime?: number; + status?: string; + tableName?: string; + type?: SubTaskExecuteType; + userCondition?: string; +}[]; +export type IDataDeleteSubTaskExecutionDetails = { + endTime?: number; + processedRowCount?: number; + processedRowsPerSecond?: number; + readRowCount?: number; + readRowsPerSecond?: number; + startTime?: number; + status?: string; + tableName?: string; + type?: SubTaskExecuteType; + userCondition?: string; +}[]; +export type IPartitionPlanSubTaskExecutionDetails = { + cloudProvider?: string; + csvResultSetZipDownloadUrl?: string; + errorRecordsFileDownloadUrl?: string; + failedRecord?: string[]; + failedStatements?: number; + finishedStatements?: number; + region?: string; + sqlExecuteJsonFileDownloadUrl?: string; + succeedStatements?: number; + totalStatements?: number; +}; +export type ISqlPlanSubTaskExecutionDetails = { + cloudProvider?: string; + csvResultSetZipDownloadUrl?: string; + errorRecordsFileDownloadUrl?: string; + failedRecord?: string[]; + failedStatements?: number; + finishedStatements?: number; + region?: string; + sqlExecuteJsonFileDownloadUrl?: string; + succeedStatements?: number; + totalStatements?: number; +}; + +export type SubTaskParameters = + | IPartitionPlanSubTaskParameters + | ISqlPlanParametersSubTaskParameters + | IDataClearParametersSubTaskParameters + | IDataArchiveParametersSubTaskParameters; + +export type IPartitionPlanSubTaskParameters = { + delimiter?: string; + errorStrategy?: string; + queryLimit?: number; + retryIntervalMillis?: number; + retryTimes?: number; + sessionTimeZone?: string; + sqlContent?: string; + timeoutMillis?: number; +}; +export type ISqlPlanParametersSubTaskParameters = { + databaseId?: number; + databaseInfo?: IDatabase; + delimiter?: string; + errorStrategy?: string; + generateRollbackPlan?: string; + markAsFailedWhenAnyErrorsHappened?: boolean; + modifyTimeoutIfTimeConsumingSqlExists?: boolean; + parentScheduleType?: string; + queryLimit?: number; + retryIntervalMillis?: number; + retryTimes?: number; + riskLevelIndex?: number; + rollbackSqlContent?: string; + rollbackSqlObjectIds?: string[]; + rollbackSqlObjectNames?: string[]; + sqlContent?: string; + sqlObjectIds?: string[]; + sqlObjectNames?: string[]; + timeoutMillis?: number; +}; +export type IDataClearParametersSubTaskParameters = Record; +export type IDataArchiveParametersSubTaskParameters = Record; + +export interface scheduleTask { createTime: number; id: number; jobGroup: ScheduleType; status: ScheduleTaskStatus; type: SubTaskType; updateTime: number; - executionDetails: any; + executionDetails?: K; project: IProject; currentUserResourceRoles?: ProjectRole[]; scheduleId?: number; scheduleName?: string; lastExecutionTime?: number; jobName?: string; - parameters: any; + parameters: T; creator?: { id: number; name: string; diff --git a/src/page/Schedule/const.tsx b/src/page/Schedule/const.tsx index 717c6bcc2..234ddda32 100644 --- a/src/page/Schedule/const.tsx +++ b/src/page/Schedule/const.tsx @@ -3,6 +3,7 @@ import { SchedulePageType } from '@/d.ts/schedule'; import { formatMessage } from '@/util/intl'; import settingStore from '@/store/setting'; import { SchedulePageTextMap } from '@/constant/schedule'; +import { isClient } from '@/util/env'; export interface ITaskModeConfig { pageType: SchedulePageType; @@ -14,7 +15,7 @@ const schedlueConfig: PartialTaskConfig = { [SchedulePageType.ALL]: { label: SchedulePageTextMap[SchedulePageType.ALL], pageType: SchedulePageType.ALL, - enabled: () => true, + enabled: () => !isClient(), }, [SchedulePageType.DATA_ARCHIVE]: { label: SchedulePageTextMap[SchedulePageType.DATA_ARCHIVE], diff --git a/src/page/ScheduleCreatePage/index.tsx b/src/page/ScheduleCreatePage/index.tsx index ed2d4377f..c123f9e44 100644 --- a/src/page/ScheduleCreatePage/index.tsx +++ b/src/page/ScheduleCreatePage/index.tsx @@ -34,6 +34,7 @@ const ScheduleCreatePage: React.FC = ({ scheduleStore }) => { return ( = (props) => { - const { userStore } = props; const context = useContext(ActivityBarContext); const items: IItem[] = [ @@ -78,7 +77,7 @@ const ActivityBar: React.FC = (props) => { title: ActivityBarItemTypeText[ActivityBarItemType.Schedule], key: ActivityBarItemType.Schedule, icon: ScheduleSvg, - isVisible: true, + isVisible: !isClient(), }, { title: ActivityBarItemTypeText[ActivityBarItemType.Manager], diff --git a/src/page/Workspace/components/CreateSchedule/index.less b/src/page/Workspace/components/CreateSchedule/index.less index f65c22a38..33a926bcc 100644 --- a/src/page/Workspace/components/CreateSchedule/index.less +++ b/src/page/Workspace/components/CreateSchedule/index.less @@ -1,4 +1,4 @@ .CreatePageContainer { - padding: 12px 0px 12px 24px; + padding: 12px 6px 12px 0px; height: 100%; } diff --git a/src/page/Workspace/index.tsx b/src/page/Workspace/index.tsx index 9761595c2..cc5ba51ee 100644 --- a/src/page/Workspace/index.tsx +++ b/src/page/Workspace/index.tsx @@ -43,7 +43,7 @@ import { isString, toInteger } from 'lodash'; import { isGroupNode } from '@/page/Workspace/SideBar/ResourceTree/const'; import { inject, observer } from 'mobx-react'; import React, { useContext, useEffect, useState } from 'react'; -import ActivityBar from './ActivityBar/ index'; +import ActivityBar from './ActivityBar'; import ResourceTreeContext from './context/ResourceTreeContext'; import WorkspaceStore from './context/WorkspaceStore'; import GlobalModals from './GlobalModals'; From 525edeb67ca2f2ebf0ae540e8f082f63767d8c94 Mon Sep 17 00:00:00 2001 From: xiaokang Date: Tue, 19 Aug 2025 14:15:14 +0800 Subject: [PATCH 021/239] chore: fix oic --- package.json | 4 ++-- pnpm-lock.yaml | 32 +++++++++++++++++++------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index e3518c478..7c728c9a9 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "dev:client": "concurrently \"UMI_ENV=client npm run dev\" \"npm run build-main-dev\"", "postinstall": "node scripts/plugin/initPlugins.js && max setup", "oic:clear": "oic --configPath ./scripts/must.js --clear", - "oic:extract": "oic --configPath ./scripts/must.js --extract all", + "oic:extract": "oic --configPath ./scripts/must.js --parallel --extract all", "oic:migrate": "oic --configPath ./scripts/must.js --migrate", "prepack": "node ./scripts/clientDependencies/index.js", "pack-client:all": "node ./scripts/client/build.js all", @@ -76,7 +76,7 @@ "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@oceanbase-odc/monaco-plugin-ob": "~1.4.2", - "@oceanbase-odc/ob-intl-cli": "^2.1.3", + "@oceanbase-odc/ob-intl-cli": "^2.2.0", "@oceanbase-odc/ob-parser-js": "^3.0.5", "@oceanbase-odc/ob-react-data-grid": "^4.0.0", "@sentry/react": "^7.88.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a9bc8e53..42e857802 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,8 +55,8 @@ importers: specifier: ~1.4.2 version: 1.4.2(monaco-editor@0.36.1) '@oceanbase-odc/ob-intl-cli': - specifier: ^2.1.3 - version: 2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2) + specifier: ^2.2.0 + version: 2.2.0(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2) '@oceanbase-odc/ob-parser-js': specifier: ^3.0.5 version: 3.0.5 @@ -1183,6 +1183,10 @@ packages: resolution: {integrity: sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.3': + resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -1969,8 +1973,8 @@ packages: peerDependencies: monaco-editor: ~0.38.0 - '@oceanbase-odc/ob-intl-cli@2.1.4': - resolution: {integrity: sha512-AKoGr7cWn3AyvTYB2xoFZtfikBgyRxDWUya1IXbVGZeoBd4GGE7JcwFLmFplYNmtUFrOhyP7Vdkz2SNFAH6gQg==} + '@oceanbase-odc/ob-intl-cli@2.2.0': + resolution: {integrity: sha512-qCJriKatOEwHICu9sGM5b7PYYtWxtVtB1V+P/ZBTwACXQHhQEgFlBCnt9CDAoMIOM9iDEeFvV13VSWt/kGW8ig==} hasBin: true '@oceanbase-odc/ob-parser-js@3.0.5': @@ -11486,6 +11490,8 @@ snapshots: '@babel/runtime@7.27.4': {} + '@babel/runtime@7.28.3': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -12298,7 +12304,7 @@ snapshots: comlink: 4.4.2 monaco-editor: 0.36.1 - '@oceanbase-odc/ob-intl-cli@2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2)': + '@oceanbase-odc/ob-intl-cli@2.2.0(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2)': dependencies: '@babel/core': 7.27.4 '@babel/generator': 7.27.3 @@ -13057,7 +13063,7 @@ snapshots: '@types/history@5.0.0': dependencies: - history: 5.3.0 + history: 4.10.1 '@types/hoist-non-react-statics@3.3.6': dependencies: @@ -13144,7 +13150,7 @@ snapshots: '@types/history': 4.7.11 '@types/react': 16.14.65 '@types/react-router': 5.1.20 - redux: 4.2.1 + redux: 3.7.2 '@types/react-router@5.1.20': dependencies: @@ -15995,7 +16001,7 @@ snapshots: dva-core@1.5.0-beta.2(redux@3.7.2): dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.3 flatten: 1.0.3 global: 4.4.0 invariant: 2.2.4 @@ -16028,7 +16034,7 @@ snapshots: dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2): dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.3 '@types/isomorphic-fetch': 0.0.34 '@types/react-router-dom': 4.3.5 '@types/react-router-redux': 5.0.27 @@ -17532,7 +17538,7 @@ snapshots: history@4.10.1: dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.3 loose-envify: 1.4.0 resolve-pathname: 3.0.0 tiny-invariant: 1.3.3 @@ -19758,7 +19764,7 @@ snapshots: lodash: 4.17.21 postcss: 8.5.4 - 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-syntax@0.36.2(postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.4))(postcss@7.0.39))(postcss-less@3.1.4)(postcss-scss@2.1.1)(postcss@7.0.39): dependencies: postcss: 7.0.39 optionalDependencies: @@ -20767,7 +20773,7 @@ snapshots: react-redux@5.1.2(react@17.0.2)(redux@3.7.2): dependencies: - '@babel/runtime': 7.27.4 + '@babel/runtime': 7.28.3 hoist-non-react-statics: 3.3.2 invariant: 2.2.4 loose-envify: 1.4.0 @@ -21956,7 +21962,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-less@3.1.4)(postcss-scss@2.1.1)(postcss@7.0.39) + postcss-syntax: 0.36.2(postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.4))(postcss@7.0.39))(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 From d9e46bb9e33ca7f2f56594c24a216733f6b0777b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Tue, 19 Aug 2025 14:26:04 +0800 Subject: [PATCH 022/239] =?UTF-8?q?PullRequest:=20948=20=E8=A1=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=B1=95=E7=A4=BA=E6=95=B0=E6=8D=AE=E9=87=8F=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/tableDataLimit of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/948 Reviewed-by: 晓康 * fix: onBlur时触发refresh * feat: 按 sessionID 记录用户设置的值 * feat: 卸载时按sessionId清理limitCount * fix: 删除多余代码 * feat: 将 limit 维护在 tableData 中 * refactor: 删除多余代码 * feat: 完善limit 设置 * refactor: 删除多余代码 * fix: 修复 state 设置 * refactor: 调整limit设置 * refactor: 删除多余代码 * refactor: 删除多余空行 * refactor: 删除多余注释 --- .../components/DDLResultSet/index.tsx | 29 +++++++++++++------ .../components/TablePage/TableData/index.tsx | 7 ++++- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/page/Workspace/components/DDLResultSet/index.tsx b/src/page/Workspace/components/DDLResultSet/index.tsx index 3210da10f..c67543a22 100644 --- a/src/page/Workspace/components/DDLResultSet/index.tsx +++ b/src/page/Workspace/components/DDLResultSet/index.tsx @@ -158,6 +158,10 @@ interface IProps { * db 查询耗时 */ dbTotalDurationMicroseconds?: number; + /** + * 外部传入的初始limit值,优先级高于session中的queryLimit + */ + initialLimit?: number; onRefresh?: (limit: number) => void; onSubmitRows?: ( newRows, @@ -177,6 +181,7 @@ interface IProps { ) => void; isExternalTable?: boolean; // 是否为外表 } + const DDLResultSet: React.FC = function (props) { const { isTableData, @@ -206,6 +211,7 @@ const DDLResultSet: React.FC = function (props) { withFullLinkTrace = false, withQueryProfile = false, traceEmptyReason = '', + initialLimit, onUpdateEditing, onRefresh, onShowExecuteDetail, @@ -229,7 +235,9 @@ const DDLResultSet: React.FC = function (props) { /** * 数据量限制 */ - const [limit, setLimit] = useState(session?.params?.queryLimit); + const [limit, setLimit] = useState(() => { + return initialLimit ?? session?.params?.queryLimit; + }); /** * 表数据搜索 */ @@ -402,7 +410,7 @@ const DDLResultSet: React.FC = function (props) { gridRef.current?.scrollToRow(0); }, [gridRef]); const handleExport = useCallback(() => { - onExport?.(limit || 1000); + onExport?.(limit); }, [onExport, limit]); const handleEditPropertyInCell = useCallback( (newRows) => { @@ -856,7 +864,7 @@ const DDLResultSet: React.FC = function (props) { { - onSubmitRows?.(editRows, limit || 1000, true, table.columns); + onSubmitRows?.(editRows, limit, true, table.columns); }} > = function (props) { onClick={async () => { setIsSubmitting(true); try { - await onSubmitRows?.(editRows, limit || 1000, false, table.columns); + await onSubmitRows?.(editRows, limit, false, table.columns); } finally { setIsSubmitting(false); } @@ -965,7 +973,7 @@ const DDLResultSet: React.FC = function (props) { key="commit" onConfirm={async () => { await sqlStore.commit(props.pageKey, sessionId, session?.database?.dbName); - onRefresh(limit || 1000); + onRefresh(limit); }} disabled={isInTransaction} > @@ -981,7 +989,7 @@ const DDLResultSet: React.FC = function (props) { key="rollback" onConfirm={async () => { await sqlStore.rollback(props.pageKey, sessionId, session?.database?.dbName); - onRefresh(limit || 1000); + onRefresh(limit); }} isRollback disabled={isInTransaction} @@ -1248,13 +1256,16 @@ const DDLResultSet: React.FC = function (props) { }} min={1} precision={0} - defaultValue={session?.params?.queryLimit} + defaultValue={limit} style={{ width: 70, marginLeft: 8, }} + onBlur={() => { + onRefresh(limit); + }} onPressEnter={() => { - onRefresh(limit || 1000); + onRefresh(limit); }} /> @@ -1345,7 +1356,7 @@ const DDLResultSet: React.FC = function (props) { defaultMessage: '刷新', })} icon={} - onClick={onRefresh.bind(this, limit || 1000)} + onClick={onRefresh.bind(this, limit)} /> ) : null} diff --git a/src/page/Workspace/components/TablePage/TableData/index.tsx b/src/page/Workspace/components/TablePage/TableData/index.tsx index d9a476d1d..5ab6e6379 100644 --- a/src/page/Workspace/components/TablePage/TableData/index.tsx +++ b/src/page/Workspace/components/TablePage/TableData/index.tsx @@ -72,6 +72,7 @@ class TableData extends React.Component< status: EStatus; hasExecuted: boolean; allowExport?: boolean; + limit: number; } > { constructor(props) { @@ -88,6 +89,7 @@ class TableData extends React.Component< status: null, hasExecuted: false, allowExport: true, + limit: props.session?.params?.queryLimit || 1000, }; } @@ -112,7 +114,7 @@ class TableData extends React.Component< public reloadTableData = async ( tableName: string, keepInitialSQL: boolean = false, - limit: number = 1000, + limit: number = this.state.limit, ) => { const { table, session } = this.props; @@ -137,6 +139,7 @@ class TableData extends React.Component< track: data?.track, }); } + this.setState({ limit }); let resultSet = generateResultSetColumns([data], session?.connection?.dialectType)?.[0]; if (resultSet) { this._resultSetKey = generateUniqKey(); @@ -393,6 +396,7 @@ class TableData extends React.Component< lintResultSet, status, allowExport, + limit, } = this.state; return ( @@ -409,6 +413,7 @@ class TableData extends React.Component< table={{ ...table, columns: resultSet.resultSetMetaData?.columnList }} pageKey={pageKey} session={session} + initialLimit={limit} onUpdateEditing={(editing) => { this.setState({ isEditing: editing, From 05c6c62fd745a1c1dcde6daaaf2c746db6310ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Tue, 19 Aug 2025 14:53:02 +0800 Subject: [PATCH 023/239] =?UTF-8?q?PullRequest:=20949=20feat=EF=BC=9A?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=A0=91=E5=B1=95=E7=A4=BA=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=97=B6=E5=A2=9E=E5=8A=A0=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/resourceTreeStyle_441 of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/949 Reviewed-by: 晓康 * feat: 资源树增加交互样式 * fix: 下拉菜单增加样式 --- src/component/Action/Group.tsx | 3 +++ .../ResourceTree/TreeNodeMenu/dataSource.tsx | 4 +++- .../ResourceTree/TreeNodeMenu/index.tsx | 5 +++++ .../Workspace/SideBar/ResourceTree/index.less | 22 +++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) 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/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/dataSource.tsx b/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/dataSource.tsx index 49fff4929..d3a1c58fe 100644 --- a/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/dataSource.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/dataSource.tsx @@ -106,6 +106,7 @@ const CustomDropdown = ({ overlay={menu} trigger={['contextMenu']} open={dropdownVisible} + destroyOnHidden onVisibleChange={setDropdownVisible} placement="bottomLeft" > @@ -171,6 +172,7 @@ const DataSourceNodeMenu = (props: IProps) => { <> } @@ -222,7 +224,7 @@ const DataSourceNodeMenu = (props: IProps) => { /> )} {userStore.isPrivateSpace() && ( - + { setCopyDatasourceId(dataSource.id); diff --git a/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/index.tsx b/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/index.tsx index 404a97c68..ecee290fa 100644 --- a/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/index.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/index.tsx @@ -277,6 +277,8 @@ const TreeNodeMenu = (props: IProps) => { onMenuClick(clickMap[info.key]); }, }} + overlayClassName={treeStyles.dropdownMenu} + destroyOnHidden trigger={['hover']} >
@@ -303,6 +305,7 @@ const TreeNodeMenu = (props: IProps) => { { }, }} trigger={['contextMenu']} + overlayClassName={treeStyles.dropdownMenu} + destroyOnHidden > {nodeChild} diff --git a/src/page/Workspace/SideBar/ResourceTree/index.less b/src/page/Workspace/SideBar/ResourceTree/index.less index 4ab31dfc0..cd2aa8457 100644 --- a/src/page/Workspace/SideBar/ResourceTree/index.less +++ b/src/page/Workspace/SideBar/ResourceTree/index.less @@ -175,6 +175,15 @@ justify-content: center; } } + + .ant-tree-treenode:has( + > .ant-tree-node-content-wrapper > .ant-tree-title .ant-dropdown-open + ) { + &:not(.ant-tree-treenode-selected) { + background-color: var(--hover-color2); + } + } + .ant-tree-node-content-wrapper { display: flex; flex: 1; @@ -218,3 +227,16 @@ padding: 4px 12px; } } + +.dropdownMenu { + :global { + .ant-dropdown-menu { + .ant-dropdown-menu-submenu { + transition: background-color 0.3s ease; + } + .ant-dropdown-menu-submenu-open:not(:hover) { + background-color: var(--hover-color); + } + } + } +} From 46f0c000cc50e7a14b47d19aa391f51c6fd4c5de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=95=85=E6=99=9A?= Date: Tue, 19 Aug 2025 16:01:16 +0800 Subject: [PATCH 024/239] =?UTF-8?q?PullRequest:=20950=20fix(importtask):?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=E5=8F=AF=E5=AF=BC=E5=85=A5=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E7=9A=84=E9=80=BB=E8=BE=91=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/import of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/950 Reviewed-by: 晓康 * fix(importtask): 修复可导入对应的逻辑判断 --- .../ImportModal/ImportPreviewTable.tsx | 30 +++---------------- .../Task/component/ImportModal/index.tsx | 2 ++ src/d.ts/importTask.ts | 14 ++------- 3 files changed, 8 insertions(+), 38 deletions(-) diff --git a/src/component/Task/component/ImportModal/ImportPreviewTable.tsx b/src/component/Task/component/ImportModal/ImportPreviewTable.tsx index 10ed18c72..f708bd011 100644 --- a/src/component/Task/component/ImportModal/ImportPreviewTable.tsx +++ b/src/component/Task/component/ImportModal/ImportPreviewTable.tsx @@ -50,11 +50,7 @@ const ImportPreviewTable: React.FC = ({ ) => { let groupKey: ScheduleNonImportableType | 'TO_BE_IMPORTED'; - if ( - item.importable || - item.nonImportableType === ScheduleNonImportableType.DATASOURCE_NON_EXIST || - item.nonImportableType === ScheduleNonImportableType.LACK_OF_INSTANCE - ) { + if (item.importable) { groupKey = 'TO_BE_IMPORTED'; } else { groupKey = item.nonImportableType; @@ -97,15 +93,7 @@ const ImportPreviewTable: React.FC = ({ // 判断工单是否应该被选中 const shouldBeSelected = useCallback( (item: IImportScheduleTaskView) => { - if (item.importable) return true; - - if ( - item.nonImportableType === ScheduleNonImportableType.DATASOURCE_NON_EXIST || - item.nonImportableType === ScheduleNonImportableType.LACK_OF_INSTANCE - ) { - return hasSelectedAllDatabases(item); - } - + if (item.importable && hasSelectedAllDatabases(item)) return true; return false; }, [hasSelectedAllDatabases], @@ -196,12 +184,7 @@ const ImportPreviewTable: React.FC = ({ setSelectedRowKeys(selectedRowKeys as string[]); }, getCheckboxProps: (record) => ({ - disabled: !( - record.importable || - ((record.nonImportableType === ScheduleNonImportableType.DATASOURCE_NON_EXIST || - record.nonImportableType === ScheduleNonImportableType.LACK_OF_INSTANCE) && - hasSelectedAllDatabases(record)) - ), + disabled: !hasSelectedAllDatabases(record), }), }} /> @@ -303,12 +286,7 @@ const ImportPreviewTable: React.FC = ({ )} {Object.keys(ScheduleNonImportableType) - ?.filter( - (key) => - key !== ScheduleNonImportableType.DATASOURCE_NON_EXIST && - key !== ScheduleNonImportableType.LACK_OF_INSTANCE && - groupedData[key as ScheduleNonImportableType]?.length > 0, - ) + ?.filter((key) => groupedData[key as ScheduleNonImportableType]?.length > 0) ?.map((key) => { return ( diff --git a/src/component/Task/component/ImportModal/index.tsx b/src/component/Task/component/ImportModal/index.tsx index bf621a50f..1688ad853 100644 --- a/src/component/Task/component/ImportModal/index.tsx +++ b/src/component/Task/component/ImportModal/index.tsx @@ -278,6 +278,7 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy createdList: [], }); setPreviewData([]); + setIsConfirm(false); }; useEffect(() => { @@ -342,6 +343,7 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy setNotConfirmButSubmit(false); setDatabaseSelections({}); setSelectedRowKeys([]); + setIsConfirm(false); }} > {formatMessage({ diff --git a/src/d.ts/importTask.ts b/src/d.ts/importTask.ts index e77e09166..d034c588b 100644 --- a/src/d.ts/importTask.ts +++ b/src/d.ts/importTask.ts @@ -3,21 +3,11 @@ import { ConnectType, IConnection, TaskStatus, TaskType } from '.'; import { ODCCloudProvider } from './migrateTask'; export enum ScheduleNonImportableType { - LACK_OF_INSTANCE = 'LACK_OF_INSTANCE', TYPE_NOT_MATCH = 'TYPE_NOT_MATCH', - DATASOURCE_NON_EXIST = 'DATASOURCE_NON_EXIST', IMPORTED = 'IMPORTED', -} // 前端维护的待导入, 包含DATASOURCE_NON_EXIST, LACK_OF_INSTANCE和可导入的 -// TO_BE_IMPORTED = 'TO_BE_IMPORTED', +} + export const ScheduleNonImportableTypeMap = { - [ScheduleNonImportableType.DATASOURCE_NON_EXIST]: formatMessage({ - id: 'src.d.ts.25D43093', - defaultMessage: '数据源不存在', - }), - [ScheduleNonImportableType.LACK_OF_INSTANCE]: formatMessage({ - id: 'src.d.ts.CAC52ADB', - defaultMessage: '实例不存在', - }), [ScheduleNonImportableType.TYPE_NOT_MATCH]: formatMessage({ id: 'src.d.ts.B89ABE6D', defaultMessage: '类型不匹配', From d94fe29e6b7c57b9a4b16e7cea2eb919f342dbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=95=85=E6=99=9A?= Date: Wed, 20 Aug 2025 16:11:10 +0800 Subject: [PATCH 025/239] =?UTF-8?q?PullRequest:=20952=20fix(terminate):=20?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E5=BC=B9=E7=AA=97=E6=97=B6=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E5=86=85=E9=83=A8=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/confirm of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/952 Reviewed-by: 晓康 * fix(terminate): 关闭弹窗时初始化内部状态 * fix(terminate): add cancel funtion --- .../Task/component/AsyncTaskOperationButton/index.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/component/Task/component/AsyncTaskOperationButton/index.tsx b/src/component/Task/component/AsyncTaskOperationButton/index.tsx index 8517aa923..4e2c20f06 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/index.tsx +++ b/src/component/Task/component/AsyncTaskOperationButton/index.tsx @@ -112,6 +112,12 @@ export function AsyncTaskOperationButton(props: IAsyncTaskOperationConfig) { showModal(); }; + const cancel = () => { + setVisible(false); + setRiskConfirmed(false); + setConfirmRiskUnFinished(false); + }; + return ( <> @@ -156,14 +162,14 @@ export function AsyncTaskOperationButton(props: IAsyncTaskOperationConfig) { )} - setVisible(false)} + closeModal={cancel} disabled={taskListThatCanBeAction?.length === 0} tasks={taskListThatCanBeAction} asyncTaskType={props.asyncTaskType} From b4a364aee34c6b36a745bac85b4c0ad710c10f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Wed, 20 Aug 2025 16:49:08 +0800 Subject: [PATCH 026/239] =?UTF-8?q?PullRequest:=20953=20feat:=20=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E4=BD=9C=E4=B8=9A=E7=BB=9F=E4=B8=80=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/scheduleOptimize2 of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/953 Reviewed-by: 晓康 * feat: 抽离执行方式组件,作业执行方式默认改成周期执行 * feat: 工单执行方式统一 --- src/component/AnchorContainer/index.less | 2 +- .../SchduleExecutionMethodForm/index.tsx | 91 +++++++++++++++ .../modals/DataArchive/Content/index.tsx | 34 ++---- .../modals/DataArchive/Create/index.tsx | 92 +++------------ .../modals/DataClear/Content/index.tsx | 25 ++--- .../modals/DataClear/Create/index.tsx | 81 +++----------- .../modals/PartitionPlan/Content/index.tsx | 16 +-- .../modals/PartitionPlan/Create/index.tsx | 105 +++--------------- .../Schedule/modals/SQLPlan/Content/index.tsx | 16 +-- .../Schedule/modals/SQLPlan/Create/index.tsx | 97 +++------------- .../Task/component/TaskActions/index.tsx | 21 +++- .../index.tsx | 34 ++---- src/component/Task/const.ts | 23 +--- src/component/Task/hooks/useLoadProjects.tsx | 4 + .../modals/AlterDdlTask/CreateModal/index.tsx | 6 +- .../CreateModal/index.tsx | 18 ++- .../ApplyPermission/CreateModal/index.tsx | 6 +- .../modals/AsyncTask/CreateModal/index.tsx | 8 +- .../DataMockerTask/CreateModal/form.tsx | 6 +- .../DataMockerTask/CreateModal/index.tsx | 1 - .../ExportForm/ConfigPanel/index.tsx | 4 +- .../modals/ExportTask/CreateModal/index.tsx | 2 +- .../ImportForm/ConfigPanel/index.tsx | 4 +- .../modals/ImportTask/CreateModal/index.tsx | 2 +- .../CreateModal/index.tsx | 13 ++- .../CreateModal/MoreSetting.tsx | 37 +----- .../MutipleAsyncTask/CreateModal/index.tsx | 19 ++++ .../MutipleAsyncTask/DetailContent/index.tsx | 18 ++- .../ResultSetExportTask/CreateModal/index.tsx | 6 +- .../CreateModal/StructConfigPanel/index.tsx | 4 +- .../ShadowSyncTask/CreateModal/index.tsx | 2 +- .../CreateModal/index.tsx | 41 +++---- .../DetailContent/index.tsx | 14 +++ src/d.ts/index.ts | 1 + .../DatabaseSearchModal/index.less | 7 ++ .../DatabaseSearchModal/index.tsx | 43 +++---- .../ResourceTree/TreeNodeMenu/dataSource.tsx | 19 ++-- .../hooks/useDataSourceDrawer.tsx | 7 +- .../Workspace/SideBar/ResourceTree/index.tsx | 25 ++--- 39 files changed, 389 insertions(+), 565 deletions(-) create mode 100644 src/component/Schedule/components/SchduleExecutionMethodForm/index.tsx rename src/component/Task/component/{TimerSelect => TaskExecutionMethodForm}/index.tsx (79%) diff --git a/src/component/AnchorContainer/index.less b/src/component/AnchorContainer/index.less index bbd1a632e..5384c5135 100644 --- a/src/component/AnchorContainer/index.less +++ b/src/component/AnchorContainer/index.less @@ -8,7 +8,7 @@ } .anchor { - padding: 12px 22px 0px 6px; + padding: 6px 16px 12px 12px; height: 100%; position: sticky; right: 0; diff --git a/src/component/Schedule/components/SchduleExecutionMethodForm/index.tsx b/src/component/Schedule/components/SchduleExecutionMethodForm/index.tsx new file mode 100644 index 000000000..bac452a05 --- /dev/null +++ b/src/component/Schedule/components/SchduleExecutionMethodForm/index.tsx @@ -0,0 +1,91 @@ +import { DatePicker, Form } from 'antd'; +import { formatMessage } from '@/util/intl'; +import { TaskExecStrategy } from '@/d.ts'; +import { Radio } from 'antd'; +import { FieldTimeOutlined } from '@ant-design/icons'; +import Crontab from '@/component/Crontab'; +import { disabledDate, disabledTime } from '@/util/utils'; +import { forwardRef } from 'react'; +import { ICrontab } from '@/component/Crontab/interface'; + +interface TriggerStrategyFormProps { + crontab?: ICrontab; + handleCrontabChange?: (value: ICrontab) => void; +} + +const SchduleExecutionMethodForm = forwardRef< + { + setValue: (value: ICrontab) => void; + resetFields: () => void; + }, + TriggerStrategyFormProps +>((props, ref) => { + const { crontab, handleCrontabChange } = props; + + return ( + <> + + + + { + formatMessage({ + id: 'odc.DataArchiveTask.CreateModal.ExecuteNow', + defaultMessage: '立即执行', + }) /*立即执行*/ + } + + + { + formatMessage({ + id: 'odc.DataArchiveTask.CreateModal.ScheduledExecution', + defaultMessage: '定时执行', + }) /*定时执行*/ + } + + + { + formatMessage({ + id: 'odc.DataArchiveTask.CreateModal.PeriodicExecution', + defaultMessage: '周期执行', + }) /*周期执行*/ + } + + + + + {({ getFieldValue }) => { + const triggerStrategy = getFieldValue('triggerStrategy'); + if (triggerStrategy === TaskExecStrategy.START_AT) { + return ( + + } + disabledDate={disabledDate} + disabledTime={disabledTime} + /> + + ); + } + if (triggerStrategy === TaskExecStrategy.TIMER) { + return ( + + + + ); + } + return null; + }} + + + ); +}); + +export default SchduleExecutionMethodForm; diff --git a/src/component/Schedule/modals/DataArchive/Content/index.tsx b/src/component/Schedule/modals/DataArchive/Content/index.tsx index 498978b8d..bb30ad9aa 100644 --- a/src/component/Schedule/modals/DataArchive/Content/index.tsx +++ b/src/component/Schedule/modals/DataArchive/Content/index.tsx @@ -91,38 +91,22 @@ const DataArchiveScheduleContent: React.FC = (props) => { )} -
- -
- } - /> -
-
+ } + />
-
- -
- } - /> -
-
+ } + />
- + {!login.isPrivateSpace() && ( diff --git a/src/component/Schedule/modals/DataArchive/Create/index.tsx b/src/component/Schedule/modals/DataArchive/Create/index.tsx index 1426f7f2e..791c2f64a 100644 --- a/src/component/Schedule/modals/DataArchive/Create/index.tsx +++ b/src/component/Schedule/modals/DataArchive/Create/index.tsx @@ -1,9 +1,7 @@ import { getTableListByDatabaseName } from '@/common/network/table'; import { previewSqlStatements } from '@/common/network/task'; -import Crontab from '@/component/Crontab'; import { CrontabDateType, CrontabMode, ICrontab } from '@/component/Crontab/interface'; import FormItemPanel from '@/component/FormItemPanel'; -import DescriptionInput from '@/component/Task/component/DescriptionInput'; import { IDatabase } from '@/d.ts/database'; import { useRequest } from 'ahooks'; import HelpDoc from '@/component/helpDoc'; @@ -17,11 +15,9 @@ import { import { createSchedule, updateSchedule, getScheduleDetail } from '@/common/network/schedule'; import { SchedulePageType, ScheduleType } from '@/d.ts/schedule'; import { useDBSession } from '@/store/sessionManager/hooks'; -import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; import { hourToMilliSeconds, kbToMb, mbToKb, milliSecondsToHour } from '@/util/utils'; -import { FieldTimeOutlined } from '@ant-design/icons'; -import { Button, Checkbox, DatePicker, Form, Modal, Radio, Space, Spin, message } from 'antd'; +import { Button, Checkbox, Form, Modal, Radio, Space, Spin, message } from 'antd'; import { inject, observer } from 'mobx-react'; import dayjs from 'dayjs'; import React, { useEffect, useRef, useState } from 'react'; @@ -32,7 +28,6 @@ import TaskdurationItem from '@/component/Task/component/TaskdurationItem'; import ThrottleFormItem from '@/component/Task/component/ThrottleFormItem'; import { isConnectTypeBeFileSystemGroup } from '@/util/connection'; import ShardingStrategyItem from '@/component/Task/component/ShardingStrategyItem'; -import { disabledDate, disabledTime } from '@/util/utils'; import DirtyRowAction from '@/component/Task/component/DirtyRowAction'; import MaxAllowedDirtyRowCount from '@/component/Task/component/MaxAllowedDirtyRowCount'; import AnchorContainer from '@/component/AnchorContainer'; @@ -49,6 +44,7 @@ import { SchedulePageMode } from '@/component/Schedule/interface'; import { openSchedulesPage } from '@/store/helper/page'; import { getDataSourceModeConfig } from '@/common/datasource'; import { ConnectTypeText } from '@/constant/label'; +import SchduleExecutionMethodForm from '@/component/Schedule/components/SchduleExecutionMethodForm'; export enum IArchiveRange { PORTION = 'portion', @@ -110,7 +106,7 @@ const getVariables = ( }; const defaultValue = { - triggerStrategy: TaskExecStrategy.START_NOW, + triggerStrategy: TaskExecStrategy.TIMER, archiveRange: IArchiveRange.PORTION, tables: [null], migrationInsertAction: MigrationInsertAction.INSERT_DUPLICATE_UPDATE, @@ -577,6 +573,7 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode })

基本信息

+ = ({ scheduleStore, projectId, pageStore, mode }) projectId={projectId} /> +

归档范围

+ = ({ scheduleStore, projectId, pageStore, mode }) )} -

执行方式

- - - - { - formatMessage({ - id: 'odc.DataArchiveTask.CreateModal.ExecuteNow', - defaultMessage: '立即执行', - }) /*立即执行*/ - } - - {!isClient() ? ( - - { - formatMessage({ - id: 'odc.DataArchiveTask.CreateModal.ScheduledExecution', - defaultMessage: '定时执行', - }) /*定时执行*/ - } - - ) : null} - - { - formatMessage({ - id: 'odc.DataArchiveTask.CreateModal.PeriodicExecution', - defaultMessage: '周期执行', - }) /*周期执行*/ - } - - - - - {({ getFieldValue }) => { - const triggerStrategy = getFieldValue('triggerStrategy'); - if (triggerStrategy === TaskExecStrategy.START_AT) { - return ( - - } - disabledDate={disabledDate} - disabledTime={disabledTime} - /> - - ); - } - if (triggerStrategy === TaskExecStrategy.TIMER) { - return ( - - - - ); - } - return null; - }} - + +

+ 执行方式 +

+ + +

作业设置

+ = (props) => { )} -
- -
- } - /> -
-
+ } + />
@@ -108,14 +100,13 @@ const DataClearScheduleContent: React.FC = (props) => { {parameters?.targetDatabase && ( <> - } /> - - {parameters?.targetDatabase?.dataSource?.name} + )} diff --git a/src/component/Schedule/modals/DataClear/Create/index.tsx b/src/component/Schedule/modals/DataClear/Create/index.tsx index 913afc37c..c79d5a414 100644 --- a/src/component/Schedule/modals/DataClear/Create/index.tsx +++ b/src/component/Schedule/modals/DataClear/Create/index.tsx @@ -1,10 +1,8 @@ import { getTableListByDatabaseName } from '@/common/network/table'; import { previewSqlStatements } from '@/common/network/task'; import { createSchedule, updateSchedule, getScheduleDetail } from '@/common/network/schedule'; -import Crontab from '@/component/Crontab'; import { CrontabDateType, CrontabMode, ICrontab } from '@/component/Crontab/interface'; import FormItemPanel from '@/component/FormItemPanel'; -import DescriptionInput from '@/component/Task/component/DescriptionInput'; import { ICycleTaskTriggerConfig, ITable, TaskExecStrategy, ShardingStrategy } from '@/d.ts'; import { history } from '@umijs/max'; import { @@ -16,11 +14,9 @@ import { SchedulePageType, } from '@/d.ts/schedule'; import { useDBSession } from '@/store/sessionManager/hooks'; -import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; import { hourToMilliSeconds, kbToMb, mbToKb, milliSecondsToHour } from '@/util/utils'; -import { FieldTimeOutlined } from '@ant-design/icons'; -import { Button, Checkbox, DatePicker, Form, Modal, Radio, Space, Spin, message } from 'antd'; +import { Button, Checkbox, Form, Modal, Radio, Space, Spin, message } from 'antd'; import { inject, observer } from 'mobx-react'; import { CreateScheduleContext } from '@/component/Schedule/context/createScheduleContext'; import dayjs from 'dayjs'; @@ -34,7 +30,6 @@ import ArchiveRange from './ArchiveRange'; import styles from './index.less'; import VariableConfig from './VariableConfig'; import ShardingStrategyItem from '@/component/Task/component/ShardingStrategyItem'; -import { disabledDate, disabledTime } from '@/util/utils'; import { useRequest } from 'ahooks'; import DirtyRowAction from '@/component/Task/component/DirtyRowAction'; import MaxAllowedDirtyRowCount from '@/component/Task/component/MaxAllowedDirtyRowCount'; @@ -44,6 +39,7 @@ import { ScheduleStore } from '@/store/schedule'; import { PageStore } from '@/store/page'; import { SchedulePageMode } from '@/component/Schedule/interface'; import { openSchedulesPage } from '@/store/helper/page'; +import SchduleExecutionMethodForm from '@/component/Schedule/components/SchduleExecutionMethodForm'; export enum IArchiveRange { PORTION = 'portion', @@ -73,7 +69,7 @@ const deleteByUniqueKeyOptions = [ ]; const defaultValue = { - triggerStrategy: TaskExecStrategy.START_NOW, + triggerStrategy: TaskExecStrategy.TIMER, archiveRange: IArchiveRange.PORTION, shardingStrategy: ShardingStrategy.AUTO, tables: [null], @@ -526,6 +522,7 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode })

基本信息

+ = ({ scheduleStore, projectId, pageStore, mode }) onChange={handleDBChange} onInit={(db) => setCreateScheduleDatabase(db)} /> +

清理范围

+ {({ getFieldValue }) => { @@ -554,71 +553,21 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) +

执行方式

- - - - { - formatMessage({ - id: 'odc.DataClearTask.CreateModal.ExecuteNow', - defaultMessage: '立即执行', - }) /*立即执行*/ - } - - {!isClient() ? ( - - { - formatMessage({ - id: 'odc.DataClearTask.CreateModal.ScheduledExecution', - defaultMessage: '定时执行', - }) /*定时执行*/ - } - - ) : null} - - { - formatMessage({ - id: 'odc.DataClearTask.CreateModal.PeriodicExecution', - defaultMessage: '周期执行', - }) /*周期执行*/ - } - - - - - {({ getFieldValue }) => { - const triggerStrategy = getFieldValue('triggerStrategy') || []; - if (triggerStrategy === TaskExecStrategy.START_AT) { - return ( - - } - disabledDate={disabledDate} - disabledTime={disabledTime} - /> - - ); - } - if (triggerStrategy === TaskExecStrategy.TIMER) { - return ( - - - - ); - } - return null; - }} - + + +

作业设置

+ diff --git a/src/component/Schedule/modals/PartitionPlan/Content/index.tsx b/src/component/Schedule/modals/PartitionPlan/Content/index.tsx index b3cf8a3e6..d779d8f12 100644 --- a/src/component/Schedule/modals/PartitionPlan/Content/index.tsx +++ b/src/component/Schedule/modals/PartitionPlan/Content/index.tsx @@ -51,18 +51,10 @@ const PartitionScheduleContent: React.FC = (props) => { defaultMessage: '数据库', })} > -
- -
- } - /> -
-
+ } + />
diff --git a/src/component/Schedule/modals/PartitionPlan/Create/index.tsx b/src/component/Schedule/modals/PartitionPlan/Create/index.tsx index b6e32e805..541e63b9a 100644 --- a/src/component/Schedule/modals/PartitionPlan/Create/index.tsx +++ b/src/component/Schedule/modals/PartitionPlan/Create/index.tsx @@ -22,7 +22,6 @@ import { Checkbox, Divider, Form, - Input, InputNumber, Modal, Radio, @@ -31,7 +30,6 @@ import { Tooltip, Typography, message, - DatePicker, } from 'antd'; import { inject, observer } from 'mobx-react'; import dayjs from 'dayjs'; @@ -58,12 +56,11 @@ import { createPartitionPlanParameters, SchedulePageType, } from '@/d.ts/schedule'; -import { FieldTimeOutlined } from '@ant-design/icons'; -import { disabledDate, disabledTime } from '@/util/utils'; import { CreateScheduleContext } from '@/component/Schedule/context/createScheduleContext'; import { PageStore } from '@/store/page'; import { SchedulePageMode } from '@/component/Schedule/interface'; import { openSchedulesPage } from '@/store/helper/page'; +import SchduleExecutionMethodForm from '@/component/Schedule/components/SchduleExecutionMethodForm'; const { Paragraph, Text } = Typography; @@ -168,6 +165,12 @@ const getCreatedTableConfigs: (tableConfigs: IPartitionTableConfig[]) => ITableC return originPartitionTableConfigs; }; +const defaultValue = { + errorStrategy: TaskErrorStrategy.ABORT, + timeoutMillis: 2, + triggerStrategy: TaskExecStrategy.TIMER, +}; + interface IProps { projectId?: number; scheduleStore?: ScheduleStore; @@ -719,19 +722,11 @@ const Create: React.FC = ({ projectId, scheduleStore, pageStore, mode }) ]} > -
+

基本信息

+ = ({ projectId, scheduleStore, pageStore, mode })
+

执行方式

- - - - { - formatMessage({ - id: 'odc.DataArchiveTask.CreateModal.ExecuteNow', - defaultMessage: '立即执行', - }) /*立即执行*/ - } - - - { - formatMessage({ - id: 'odc.DataArchiveTask.CreateModal.ScheduledExecution', - defaultMessage: '定时执行', - }) /*定时执行*/ - } - - - { - formatMessage({ - id: 'odc.DataArchiveTask.CreateModal.PeriodicExecution', - defaultMessage: '周期执行', - }) /*周期执行*/ - } - - - - - {({ getFieldValue }) => { - const triggerStrategy = getFieldValue('triggerStrategy') || []; - if (triggerStrategy === TaskExecStrategy.START_AT) { - return ( - - } - disabledDate={disabledDate} - disabledTime={disabledTime} - /> - - ); - } - if (triggerStrategy === TaskExecStrategy.TIMER) { - return ( - - { - handleCrontabChange(value); - }} - /> - - ); - } - }} - + @@ -882,16 +811,18 @@ const Create: React.FC = ({ projectId, scheduleStore, pageStore, mode }) defaultMessage: '删除策略执行周期', }) /*"删除策略执行周期"*/ } - initialValue={crontab} + initialValue={dropCrontab} onValueChange={(value) => { handleCrontabChange(value, true); }} /> )} +

作业设置

+ = (props) => { )} -
- -
- } - /> -
-
+ } + />
diff --git a/src/component/Schedule/modals/SQLPlan/Create/index.tsx b/src/component/Schedule/modals/SQLPlan/Create/index.tsx index 74b406866..c520ad047 100644 --- a/src/component/Schedule/modals/SQLPlan/Create/index.tsx +++ b/src/component/Schedule/modals/SQLPlan/Create/index.tsx @@ -55,6 +55,7 @@ import { } from '@/d.ts/schedule'; import { SchedulePageMode } from '@/component/Schedule/interface'; import { PageStore } from '@/store/page'; +import SchduleExecutionMethodForm from '@/component/Schedule/components/SchduleExecutionMethodForm'; enum ErrorStrategy { CONTINUE = 'CONTINUE', @@ -67,6 +68,7 @@ const defaultValue = { timeoutMillis: 48, errorStrategy: ErrorStrategy.ABORT, allowConcurrent: false, + triggerStrategy: TaskExecStrategy.TIMER, }; interface IProps { scheduleStore?: ScheduleStore; @@ -557,15 +559,13 @@ const Create: React.FC = ({ scheduleStore, pageStore, projectId, theme, name="basic" layout="vertical" requiredMark="optional" - initialValues={{ - ...defaultValue, - triggerStrategy: TaskExecStrategy.START_NOW, - }} + initialValues={defaultValue} onFieldsChange={handleFieldsChange} >

基本信息

+ = ({ scheduleStore, pageStore, projectId, theme,
- -

- 执行方式 -

- - - - { - formatMessage({ - id: 'odc.DataArchiveTask.CreateModal.ExecuteNow', - defaultMessage: '立即执行', - }) /*立即执行*/ - } - - - { - formatMessage({ - id: 'odc.DataArchiveTask.CreateModal.ScheduledExecution', - defaultMessage: '定时执行', - }) /*定时执行*/ - } - - - {' '} - { - formatMessage({ - id: 'odc.DataArchiveTask.CreateModal.PeriodicExecution', - defaultMessage: '周期执行', - }) /*周期执行*/ - } - - - - - {({ getFieldValue }) => { - const triggerStrategy = getFieldValue('triggerStrategy') || []; - if (triggerStrategy === TaskExecStrategy.START_AT) { - return ( - - } - disabledDate={disabledDate} - disabledTime={disabledTime} - /> - - ); - } - if (triggerStrategy === TaskExecStrategy.TIMER) { - return ( - - { - handleCrontabChange(value); - }} - /> - - ); - } - }} - -
+ +

+ 执行方式 +

+ + +

作业设置

+ = (props) => { return; } case TaskType.APPLY_DATABASE_PERMISSION: { + const detailRes = (await getTaskDetail( + task?.id, + )) as TaskDetail; modalStore.changeApplyDatabasePermissionModal(true, { - task: task as TaskDetail, + task: detailRes, }); return; } case TaskType.APPLY_PROJECT_PERMISSION: { + const detailRes = (await getTaskDetail(task?.id)) as TaskDetail; modalStore.changeApplyPermissionModal(true, { - task: task as TaskDetail, + task: detailRes, }); return; } case TaskType.APPLY_TABLE_PERMISSION: { + const detailRes = (await getTaskDetail( + task?.id, + )) as TaskDetail; modalStore.changeApplyTablePermissionModal(true, { - task: task as TaskDetail, + task: detailRes, }); return; } case TaskType.MULTIPLE_ASYNC: { + const detailRes = (await getTaskDetail(task?.id)) as TaskDetail; modalStore.changeMultiDatabaseChangeModal(true, { - projectId: (task as TaskDetail)?.parameters?.projectId, - task: task as TaskDetail, + projectId: (task as TaskDetail)?.projectId, + task: detailRes, }); return; } @@ -255,7 +263,8 @@ const TaskActions: React.FC = (props) => { return; } default: { - const { database, executionStrategy, executionTime, parameters, description } = task; + const detailRes = await getTaskDetail(task?.id); + const { database, executionStrategy, executionTime, parameters, description } = detailRes; const data = { taskType: type, diff --git a/src/component/Task/component/TimerSelect/index.tsx b/src/component/Task/component/TaskExecutionMethodForm/index.tsx similarity index 79% rename from src/component/Task/component/TimerSelect/index.tsx rename to src/component/Task/component/TaskExecutionMethodForm/index.tsx index 9653144a2..6e7e59842 100644 --- a/src/component/Task/component/TimerSelect/index.tsx +++ b/src/component/Task/component/TaskExecutionMethodForm/index.tsx @@ -14,17 +14,22 @@ * limitations under the License. */ -import { TaskExecStrategy } from '@/d.ts'; +import { TaskExecStrategy, TaskType } from '@/d.ts'; import { isClient } from '@/util/env'; import { formatMessage } from '@/util/intl'; import { FieldTimeOutlined } from '@ant-design/icons'; import { DatePicker, Form, Radio } from 'antd'; import React from 'react'; import { disabledDate, disabledTime } from '@/util/utils'; +import { getTaskExecStrategyMap } from '@/component/Task/const'; -interface IProps {} +interface IProps { + taskType?: TaskType; +} + +const TaskExecutionMethodForm: React.FC = ({ taskType }) => { + const taskExecStrategyMap = getTaskExecStrategyMap(taskType); -const TimerSelect: React.FC = (props) => { const label = formatMessage({ id: 'odc.components.TaskTimer.ExecutionMethodAfterTheApproval', defaultMessage: '执行方式:审批完成后', @@ -34,30 +39,15 @@ const TimerSelect: React.FC = (props) => { - { - formatMessage({ - id: 'odc.components.TaskTimer.ExecuteNow', - defaultMessage: '立即执行', - }) /*立即执行*/ - } + {taskExecStrategyMap?.[TaskExecStrategy.AUTO]} {!isClient() ? ( - { - formatMessage({ - id: 'odc.components.TaskTimer.ScheduledExecution', - defaultMessage: '定时执行', - }) /*定时执行*/ - } + {taskExecStrategyMap?.[TaskExecStrategy.TIMER]} ) : null} - { - formatMessage({ - id: 'odc.components.TaskTimer.ManualExecution', - defaultMessage: '手动执行', - }) /*手动执行*/ - } + {taskExecStrategyMap?.[TaskExecStrategy.MANUAL]} @@ -103,4 +93,4 @@ const TimerSelect: React.FC = (props) => { ); }; -export default TimerSelect; +export default TaskExecutionMethodForm; diff --git a/src/component/Task/const.ts b/src/component/Task/const.ts index 31ecefec5..1f4b93253 100644 --- a/src/component/Task/const.ts +++ b/src/component/Task/const.ts @@ -88,23 +88,8 @@ export const OscMaxDataSizeLimit = 1000; export const getTaskExecStrategyMap = (type: TaskType) => { switch (type) { - case TaskType.LOGICAL_DATABASE_CHANGE: { - return { - [TaskExecStrategy.AUTO]: formatMessage({ - id: 'odc.DataClearTask.CreateModal.ExecuteNow', - defaultMessage: '立即执行', - }), - [TaskExecStrategy.MANUAL]: formatMessage({ - id: 'src.component.Task.0B2B1D60', - defaultMessage: '手动执行', - }), //'手动执行' - [TaskExecStrategy.TIMER]: formatMessage({ - id: 'odc.DataClearTask.CreateModal.ScheduledExecution', - defaultMessage: '定时执行', - }), - }; - } - case TaskType.STRUCTURE_COMPARISON: { + case TaskType.STRUCTURE_COMPARISON: + case TaskType.MULTIPLE_ASYNC: { return { [TaskExecStrategy.AUTO]: formatMessage({ id: 'src.component.Task.9B79BD20', @@ -114,6 +99,10 @@ export const getTaskExecStrategyMap = (type: TaskType) => { id: 'src.component.Task.0B2B1D60', defaultMessage: '手动执行', }), //'手动执行' + [TaskExecStrategy.TIMER]: formatMessage({ + id: 'odc.components.TaskManagePage.ScheduledExecution', + defaultMessage: '定时执行', + }), //定时执行 }; } default: diff --git a/src/component/Task/hooks/useLoadProjects.tsx b/src/component/Task/hooks/useLoadProjects.tsx index 885b34f27..535dec7c0 100644 --- a/src/component/Task/hooks/useLoadProjects.tsx +++ b/src/component/Task/hooks/useLoadProjects.tsx @@ -34,6 +34,10 @@ const useLoadProjects = () => { return pre; }, {}); setProjectMap(rawProjectMap); + return res?.contents?.map(({ name, id }) => ({ + label: name, + value: id, + })); }; return { diff --git a/src/component/Task/modals/AlterDdlTask/CreateModal/index.tsx b/src/component/Task/modals/AlterDdlTask/CreateModal/index.tsx index 92f5069b1..ec2bf87e4 100644 --- a/src/component/Task/modals/AlterDdlTask/CreateModal/index.tsx +++ b/src/component/Task/modals/AlterDdlTask/CreateModal/index.tsx @@ -26,7 +26,7 @@ import CommonIDE from '@/component/CommonIDE'; import FormItemPanel from '@/component/FormItemPanel'; import HelpDoc from '@/component/helpDoc'; import DescriptionInput from '@/component/Task/component/DescriptionInput'; -import TaskTimer from '@/component/Task/component/TimerSelect'; +import TaskExecutionMethodForm from '@/component/Task/component/TaskExecutionMethodForm'; import { IAlterScheduleTaskParams, IDatasourceUser, @@ -452,7 +452,7 @@ const CreateDDLTaskModal: React.FC = (props) => { = (props) => { })} /*任务设置*/ keepExpand > - + = (props) => { const [showSelectTip, setShowSelectTip] = useState(false); const [showSelectLogicDBTip, setShowSelectLogicDBTip] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false); - const { run: getProjects, data: projects } = useRequest(listProjects, { - defaultParams: [null, null, null], - manual: true, - }); - const projectOptions = projects?.contents?.map(({ name, id }) => ({ - label: name, - value: id, - })); + const { projectOptions, loadProjects } = useLoadProjects(); const projectId = Form.useWatch('projectId', form); const disabledDate = (current) => { @@ -82,7 +76,7 @@ const CreateModal: React.FC = (props) => { useEffect(() => { if (applyDatabasePermissionVisible) { - getProjects(null, null, null); + loadProjects(); } }, [applyDatabasePermissionVisible]); const handleFieldsChange = () => { @@ -155,6 +149,10 @@ const CreateModal: React.FC = (props) => { const loadEditData = async () => { const { task } = applyDatabasePermissionData; + let tempProjectOptions = projectOptions; + if (tempProjectOptions?.length === 0) { + tempProjectOptions = await loadProjects(); + } const { parameters: { project: { id: projectId }, @@ -166,7 +164,7 @@ const CreateModal: React.FC = (props) => { }, executionStrategy, } = task; - const idProjectActive = projectOptions?.find(({ value }) => value === projectId); + const idProjectActive = tempProjectOptions?.find(({ value }) => value === projectId); const formData = { ...defaultValue, projectId: idProjectActive ? projectId : null, diff --git a/src/component/Task/modals/ApplyPermission/CreateModal/index.tsx b/src/component/Task/modals/ApplyPermission/CreateModal/index.tsx index d35652dfd..bb6b30864 100644 --- a/src/component/Task/modals/ApplyPermission/CreateModal/index.tsx +++ b/src/component/Task/modals/ApplyPermission/CreateModal/index.tsx @@ -156,6 +156,10 @@ const CreateModal: React.FC = (props) => { }; const loadEditData = async () => { + let tempProjectOptions = projectOptions; + if (tempProjectOptions?.length === 0) { + tempProjectOptions = await loadProjects(); + } const { task } = applyPermissionData; const { parameters: { @@ -165,7 +169,7 @@ const CreateModal: React.FC = (props) => { }, } = task; const formData = { - projectId: projectOptions?.find(({ value }) => value === projectId) ? projectId : null, + projectId: tempProjectOptions?.find(({ value }) => value === projectId) ? projectId : null, applyReason, resourceRoleIds: resourceRoles?.map(({ id }) => id), }; diff --git a/src/component/Task/modals/AsyncTask/CreateModal/index.tsx b/src/component/Task/modals/AsyncTask/CreateModal/index.tsx index ddfd24f80..4ba53be71 100644 --- a/src/component/Task/modals/AsyncTask/CreateModal/index.tsx +++ b/src/component/Task/modals/AsyncTask/CreateModal/index.tsx @@ -22,7 +22,7 @@ import FormItemPanel from '@/component/FormItemPanel'; import ODCDragger from '@/component/OSSDragger2'; import { ISQLLintReuslt } from '@/component/SQLLintResult/type'; import DescriptionInput from '@/component/Task/component/DescriptionInput'; -import TaskTimer from '@/component/Task/component/TimerSelect'; +import TaskExecutionMethodForm from '@/component/Task/component/TaskExecutionMethodForm'; import { RollbackType, SQLContentType, TaskExecStrategy, TaskPageType, TaskType } from '@/d.ts'; import LintResultTable from '@/page/Workspace/components/SQLResultSet/LintResultTable'; import { openTasksPage } from '@/store/helper/page'; @@ -477,7 +477,9 @@ const CreateModal: React.FC = (props) => { queryLimit: Number(setting.getSpaceConfigByKey('odc.sqlexecute.default.queryLimit')), generateRollbackPlan: setting.getSpaceConfigByKey('odc.task.default.rollbackPlanEnabled') === 'true', - executionStrategy: setting.getSpaceConfigByKey('odc.task.databaseChange.executionStrategy'), + executionStrategy: + setting.getSpaceConfigByKey('odc.task.databaseChange.executionStrategy') || + TaskExecStrategy.MANUAL, }; form.setFieldsValue(initialFormData); }; @@ -954,7 +956,7 @@ const CreateModal: React.FC = (props) => { ]} /> - + = inject('settingStore')( strategy: MockStrategy.IGNORE, totalCount: 1000, batchSize: 200, - executionStrategy: TaskExecStrategy.AUTO, + executionStrategy: TaskExecStrategy.MANUAL, databaseName, }} className={styles.mockData} @@ -360,7 +360,7 @@ const DataMockerForm: React.FC = inject('settingStore')( })} /*任务设置*/ keepExpand > - + diff --git a/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx b/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx index 6396fbc55..e55e7c905 100644 --- a/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx +++ b/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx @@ -43,7 +43,6 @@ const CreateModal: React.FC = inject('modalStore')( const [dbMode, setDbMode] = useState(null); const formRef = useRef>(null); const [ruleConfigList, setRuleConfigList] = useState([]); - const [open, setOpen] = useState(false); const loadEditData = async () => { const { task } = dataMockerData; diff --git a/src/component/Task/modals/ExportTask/CreateModal/ExportForm/ConfigPanel/index.tsx b/src/component/Task/modals/ExportTask/CreateModal/ExportForm/ConfigPanel/index.tsx index 2df89287b..f776e9ca6 100644 --- a/src/component/Task/modals/ExportTask/CreateModal/ExportForm/ConfigPanel/index.tsx +++ b/src/component/Task/modals/ExportTask/CreateModal/ExportForm/ConfigPanel/index.tsx @@ -19,7 +19,7 @@ import FormItemPanel from '@/component/FormItemPanel'; import HelpDoc from '@/component/helpDoc'; import SysFormItem from '@/component/SysFormItem'; import DescriptionInput from '@/component/Task/component/DescriptionInput'; -import TaskTimer from '@/component/Task/component/TimerSelect'; +import TaskExecutionMethodForm from '@/component/Task/component/TaskExecutionMethodForm'; import { ENABLED_SYS_FROM_ITEM } from '@/component/Task/helper'; import { EXPORT_CONTENT, EXPORT_TYPE, IConnection, IMPORT_ENCODING } from '@/d.ts'; import odc from '@/plugins/odc'; @@ -436,7 +436,7 @@ const ConfigPanel: React.FC = function ({ form, connection }) { })} /*任务设置*/ keepExpand > - + {ENABLED_SYS_FROM_ITEM && odc.appConfig.connection.sys && diff --git a/src/component/Task/modals/ExportTask/CreateModal/index.tsx b/src/component/Task/modals/ExportTask/CreateModal/index.tsx index b21d860b5..46c00b246 100644 --- a/src/component/Task/modals/ExportTask/CreateModal/index.tsx +++ b/src/component/Task/modals/ExportTask/CreateModal/index.tsx @@ -72,7 +72,7 @@ const CreateModal: React.FC = (props) => { const formData = { databaseId: modalStore.exportModalData?.databaseId, taskId: modalStore.exportModalData?.taskId, - executionStrategy: defaultConfig?.executionStrategy ?? TaskExecStrategy.AUTO, + executionStrategy: defaultConfig?.executionStrategy ?? TaskExecStrategy.MANUAL, taskName: null, dataTransferFormat: defaultConfig?.dataTransferFormat ?? EXPORT_TYPE.CSV, exportContent: defaultConfig?.exportContent ?? EXPORT_CONTENT.DATA_AND_STRUCT, diff --git a/src/component/Task/modals/ImportTask/CreateModal/ImportForm/ConfigPanel/index.tsx b/src/component/Task/modals/ImportTask/CreateModal/ImportForm/ConfigPanel/index.tsx index fe9866638..051debdaa 100644 --- a/src/component/Task/modals/ImportTask/CreateModal/ImportForm/ConfigPanel/index.tsx +++ b/src/component/Task/modals/ImportTask/CreateModal/ImportForm/ConfigPanel/index.tsx @@ -19,7 +19,7 @@ import { getTableListByDatabaseName } from '@/common/network/table'; import FormItemPanel from '@/component/FormItemPanel'; import SysFormItem from '@/component/SysFormItem'; import DescriptionInput from '@/component/Task/component/DescriptionInput'; -import TaskTimer from '@/component/Task/component/TimerSelect'; +import TaskExecutionMethodForm from '@/component/Task/component/TaskExecutionMethodForm'; import { ENABLED_SYS_FROM_ITEM } from '@/component/Task/helper'; import { ConnectionMode, EXPORT_CONTENT, IMPORT_TYPE, TaskType } from '@/d.ts'; import odc from '@/plugins/odc'; @@ -209,7 +209,7 @@ const FileSelecterPanel: React.FC = function ({ )} - + ); diff --git a/src/component/Task/modals/ImportTask/CreateModal/index.tsx b/src/component/Task/modals/ImportTask/CreateModal/index.tsx index ea17910fa..9d469ae1a 100644 --- a/src/component/Task/modals/ImportTask/CreateModal/index.tsx +++ b/src/component/Task/modals/ImportTask/CreateModal/index.tsx @@ -77,7 +77,7 @@ const CreateModal: React.FC = (props) => { useSys: false, databaseId: modalStore.importModalData?.databaseId, taskId: modalStore.exportModalData?.taskId, - executionStrategy: defaultConfig?.executionStrategy ?? TaskExecStrategy.AUTO, + executionStrategy: defaultConfig?.executionStrategy ?? TaskExecStrategy.MANUAL, fileType: defaultConfig?.fileType ?? IMPORT_TYPE.ZIP, encoding: defaultConfig?.encoding ?? IMPORT_ENCODING.UTF8, importFileName: null, diff --git a/src/component/Task/modals/LogicDatabaseAsyncTask/CreateModal/index.tsx b/src/component/Task/modals/LogicDatabaseAsyncTask/CreateModal/index.tsx index 58583d48b..90401e8f3 100644 --- a/src/component/Task/modals/LogicDatabaseAsyncTask/CreateModal/index.tsx +++ b/src/component/Task/modals/LogicDatabaseAsyncTask/CreateModal/index.tsx @@ -89,10 +89,10 @@ const CreateModal: React.FC = (props) => { const loadEditData = async (taskId) => { const dataRes = (await getTaskDetail(taskId)) as TaskDetail; setInitialSQL(dataRes?.parameters?.sqlContent); - const { parameters, description, executionStrategy, executionTime: startAt } = dataRes; + const { parameters, description, executionStrategy, executionTime } = dataRes; const { databaseId, delimiter, sqlContent, timeoutMillis } = parameters; const formData = { - startAt: undefined, + executionTime: undefined, databaseId, description, delimiter, @@ -101,7 +101,8 @@ const CreateModal: React.FC = (props) => { triggerStrategy: executionStrategy, }; if (executionStrategy === TaskExecStrategy.TIMER) { - formData.startAt = startAt && startAt > new Date().getTime() ? dayjs(startAt) : null; + formData.executionTime = + executionTime && executionTime > new Date().getTime() ? dayjs(executionTime) : null; } form.setFieldsValue(formData); }; @@ -166,7 +167,7 @@ const CreateModal: React.FC = (props) => { description, delimiter, triggerStrategy, - startAt, + executionTime, } = values; const parameters = { databaseId, @@ -179,7 +180,7 @@ const CreateModal: React.FC = (props) => { databaseId, executionStrategy: triggerStrategy, executionTime: - triggerStrategy === TaskExecStrategy.TIMER ? startAt?.valueOf() : undefined, + triggerStrategy === TaskExecStrategy.TIMER ? executionTime?.valueOf() : undefined, taskType: TaskType.LOGICAL_DATABASE_CHANGE, parameters, description, @@ -474,7 +475,7 @@ const CreateModal: React.FC = (props) => { if (triggerStrategy === TaskExecStrategy.TIMER) { return ( { const form = Form.useFormInstance(); @@ -117,35 +118,8 @@ const MoreSetting = () => { })} keepExpand > - - - - {executionStrategy === TaskExecStrategy.AUTO ? ( + + {executionStrategy === TaskExecStrategy.AUTO && ( { ]} /> - ) : ( + )} + {executionStrategy === TaskExecStrategy.MANUAL && ( = (props) => { .then(async (values) => { const { executionStrategy, + executionTime, sqlContentType, rollbackContentType, rollbackSqlFiles, @@ -376,12 +378,24 @@ const CreateModal: React.FC = (props) => { executionStrategy, parameters, description, + executionTime, }; + if (executionStrategy === TaskExecStrategy.TIMER) { + data.executionTime = executionTime?.valueOf(); + } else { + data.executionTime = undefined; + } setConfirmLoading(true); const res = await createTask(data); handleCancel(false); setConfirmLoading(false); if (res) { + message.success( + formatMessage({ + id: 'src.component.Task.LogicDatabaseAsyncTask.CreateModal.E7B4AE89', + defaultMessage: '创建成功', + }), + ); openTasksPage(TaskPageType.MULTIPLE_ASYNC); modalStore.changeMultiDatabaseChangeModal(false); } @@ -438,6 +452,7 @@ const CreateModal: React.FC = (props) => { const defaultFormData = { projectId: undefined, executionStrategy: TaskExecStrategy.MANUAL, + executionTime: undefined, retryTimes: 0, parameters: { orderedDatabaseIds: [[undefined]], @@ -454,6 +469,10 @@ const CreateModal: React.FC = (props) => { projectId: parameters?.projectId, executionStrategy, retryTimes: parameters?.retryTimes, + executionTime: + multipleAsyncTaskData?.task?.executionTime && new Date().getTime() + ? dayjs(multipleAsyncTaskData?.task?.executionTime) + : null, parameters: { orderedDatabaseIds: parameters?.orderedDatabaseIds?.length ? parameters?.orderedDatabaseIds diff --git a/src/component/Task/modals/MutipleAsyncTask/DetailContent/index.tsx b/src/component/Task/modals/MutipleAsyncTask/DetailContent/index.tsx index fe3170c78..85b717c4b 100644 --- a/src/component/Task/modals/MutipleAsyncTask/DetailContent/index.tsx +++ b/src/component/Task/modals/MutipleAsyncTask/DetailContent/index.tsx @@ -198,7 +198,20 @@ const MutipleAsyncTaskContent: React.FC = > {taskExecStrategyMap?.[task?.executionStrategy]} - {task?.executionStrategy === TaskExecStrategy.AUTO ? ( + {task?.executionStrategy === TaskExecStrategy.TIMER && ( + + {getFormatDateTime(task?.executionTime)} + + )} + {task?.executionStrategy === TaskExecStrategy.AUTO && ( = > {ErrorStrategy?.[task?.parameters?.autoErrorStrategy]} - ) : ( + )} + {task?.executionStrategy === TaskExecStrategy.MANUAL && ( = (props) => {
= (props) => { } keepExpand > - + diff --git a/src/component/Task/modals/ShadowSyncTask/CreateModal/StructConfigPanel/index.tsx b/src/component/Task/modals/ShadowSyncTask/CreateModal/StructConfigPanel/index.tsx index 607e761c8..cc004763d 100644 --- a/src/component/Task/modals/ShadowSyncTask/CreateModal/StructConfigPanel/index.tsx +++ b/src/component/Task/modals/ShadowSyncTask/CreateModal/StructConfigPanel/index.tsx @@ -21,7 +21,7 @@ import { SchemaComparingResult } from '@/d.ts'; import { formatMessage } from '@/util/intl'; import { Divider, Form, Radio } from 'antd'; import { forwardRef, useImperativeHandle } from 'react'; -import TaskTimer from '@/component/Task/component/TimerSelect'; +import TaskExecutionMethodForm from '@/component/Task/component/TaskExecutionMethodForm'; import { ErrorStrategy, IContentProps } from '../interface'; import StructAnalysisResult from './StructAnalysisResult'; import { rules } from '../const'; @@ -143,7 +143,7 @@ const StructConfigPanel = forwardRef(function ( defaultMessage: '任务设置', })} /*任务设置*/ > - + = ({ projectId, modalStore }) => const { structureComparisonVisible, structureComparisonTaskData } = modalStore; const [form] = useForm(); - const taskExecStrategyMap = getTaskExecStrategyMap(TaskType.STRUCTURE_COMPARISON); const sourceDatabaseId = Form.useWatch(['parameters', 'sourceDatabaseId'], form); const targetDatabaseId = Form.useWatch(['parameters', 'targetDatabaseId'], form); const [hasEdit, setHasEdit] = useState(false); @@ -59,6 +60,11 @@ const StructureComparisonTask: React.FC = ({ projectId, modalStore }) => async function handleSubmit() { const rawData = await form.validateFields().catch(); setConfirmLoading(true); + if (rawData?.executionStrategy === TaskExecStrategy.TIMER) { + rawData.executionTime = rawData?.executionTime?.valueOf(); + } else { + rawData.executionTime = undefined; + } rawData.taskType = TaskType.STRUCTURE_COMPARISON; const result = await createStructureComparisonTask(rawData); setConfirmLoading(false); @@ -141,10 +147,15 @@ const StructureComparisonTask: React.FC = ({ projectId, modalStore }) => form.setFieldValue(['parameters', 'targetDatabaseId'], detailRes?.relatedDatabase?.id); form.setFieldValue('description', detailRes?.description); form.setFieldValue('executionStrategy', detailRes?.executionStrategy); + form.setFieldValue( + 'executionTime', + detailRes?.executionTime && new Date().getTime() ? dayjs(detailRes?.executionTime) : null, + ); form.setFieldValue( ['parameters', 'tableNamesToBeCompared'], detailRes?.parameters?.tableNamesToBeCompared, ); + form.setFieldValue(['parameters', 'comparisonScope'], detailRes?.parameters?.comparisonScope); }; return ( @@ -189,7 +200,7 @@ const StructureComparisonTask: React.FC = ({ projectId, modalStore }) => parameters: { comparisonScope: EComparisonScope.PART, }, - executionStrategy: TaskExecStrategy.AUTO, + executionStrategy: TaskExecStrategy.MANUAL, }} onFieldsChange={handleFieldsChange} > @@ -294,29 +305,7 @@ const StructureComparisonTask: React.FC = ({ projectId, modalStore }) => } keepExpand > - - - + diff --git a/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx b/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx index fb3998e43..a7ff18b49 100644 --- a/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx +++ b/src/component/Task/modals/StructureComparisonTask/DetailContent/index.tsx @@ -34,6 +34,7 @@ import { SQLContent } from '@/component/SQLContent'; import { IResponseDataPage, SubTaskStatus, + TaskExecStrategy, TaskStatus, type ConnectType, type IStructureComparisonTaskParams, @@ -603,6 +604,19 @@ const StructureComparisonTaskContent: React.FC {getTaskExecStrategyMap(task?.type)?.[task?.executionStrategy] || '-'}
+ {task?.executionStrategy === TaskExecStrategy.TIMER && ( + + {getFormatDateTime(task?.executionTime)} + + )} { }, [searchKey, status]); const PositioninContent = useMemo(() => { - let positionText: string; + let positionText: JSX.Element; let action: () => void; if ( [ @@ -128,12 +133,12 @@ const DatabaseSearchModal = ({ modalStore, userStore }: IProps) => { SearchStatus.dataSourceWithDatabaseforObject, ].includes(status) ) { - positionText = formatMessage( - { - id: 'src.page.Workspace.SideBar.ResourceTree.DatabaseSearchModal.components.FA5E6855', - defaultMessage: '定位到数据库 "${database?.name}"', - }, - { databaseName: database?.name }, + positionText = ( +
+ 定位到数据库: + + +
); action = () => { positionResourceTree?.({ @@ -144,12 +149,12 @@ const DatabaseSearchModal = ({ modalStore, userStore }: IProps) => { }; } if ([SearchStatus.projectforObject].includes(status)) { - positionText = formatMessage( - { - id: 'src.page.Workspace.SideBar.ResourceTree.DatabaseSearchModal.E1E46959', - defaultMessage: '定位到项目{projectName}', - }, - { projectName: project?.name }, + positionText = ( +
+ 定位到项目: + + +
); action = () => { positionProjectOrDataSource?.({ @@ -159,12 +164,12 @@ const DatabaseSearchModal = ({ modalStore, userStore }: IProps) => { }; } if ([SearchStatus.dataSourceforObject].includes(status)) { - positionText = formatMessage( - { - id: 'src.page.Workspace.SideBar.ResourceTree.DatabaseSearchModal.FB02CA29', - defaultMessage: '定位到数据源{dataSourceName}', - }, - { dataSourceName: dataSource?.name }, + positionText = ( +
+ 定位到数据源: + + +
); action = () => { positionProjectOrDataSource?.({ diff --git a/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/dataSource.tsx b/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/dataSource.tsx index d3a1c58fe..6cd8bbe31 100644 --- a/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/dataSource.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/dataSource.tsx @@ -23,7 +23,7 @@ const CustomDropdown = ({ deleteDataSource, setCopyDatasourceId, setEditDatasourceId, - setAddDSVisiable, + setDataSourceDrawerVisiable, userStore, sync, }) => { @@ -51,7 +51,11 @@ const CustomDropdown = ({ defaultMessage: '克隆', }), key: 'clone', - onClick: (e) => handleMenuClick(e, () => setCopyDatasourceId(node.data.id)), + onClick: (e) => + handleMenuClick(e, () => { + setDataSourceDrawerVisiable(true); + setCopyDatasourceId(node.data.id); + }), }, { label: formatMessage({ @@ -62,7 +66,7 @@ const CustomDropdown = ({ onClick: (e) => handleMenuClick(e, () => { setEditDatasourceId(node.data.id); - setAddDSVisiable(true); + setDataSourceDrawerVisiable(true); }), }, { @@ -138,7 +142,7 @@ interface IProps { copyDatasourceId: number; setCopyDatasourceId: any; setEditDatasourceId: React.Dispatch>; - setAddDSVisiable: React.Dispatch>; + setDataSourceDrawerVisiable: React.Dispatch>; reload: () => void; } @@ -148,7 +152,7 @@ const DataSourceNodeMenu = (props: IProps) => { userStore, setCopyDatasourceId, deleteDataSource, - setAddDSVisiable, + setDataSourceDrawerVisiable, setEditDatasourceId, copyDatasourceId, reload, @@ -191,7 +195,7 @@ const DataSourceNodeMenu = (props: IProps) => { deleteDataSource={deleteDataSource} setCopyDatasourceId={setCopyDatasourceId} setEditDatasourceId={setEditDatasourceId} - setAddDSVisiable={setAddDSVisiable} + setDataSourceDrawerVisiable={setDataSourceDrawerVisiable} userStore={userStore} sync={sync} /> @@ -228,6 +232,7 @@ const DataSourceNodeMenu = (props: IProps) => { { setCopyDatasourceId(dataSource.id); + setDataSourceDrawerVisiable(true); }} key={'clone'} > @@ -245,7 +250,7 @@ const DataSourceNodeMenu = (props: IProps) => { { setEditDatasourceId(dataSource.id); - setAddDSVisiable(true); + setDataSourceDrawerVisiable(true); }} key={'edit'} > diff --git a/src/page/Workspace/SideBar/ResourceTree/hooks/useDataSourceDrawer.tsx b/src/page/Workspace/SideBar/ResourceTree/hooks/useDataSourceDrawer.tsx index 2d8af9784..79ccd3e59 100644 --- a/src/page/Workspace/SideBar/ResourceTree/hooks/useDataSourceDrawer.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/hooks/useDataSourceDrawer.tsx @@ -3,10 +3,9 @@ import { Modal, message } from 'antd'; import { formatMessage } from '@/util/intl'; import { deleteConnection } from '@/common/network/connection'; import ResourceTreeContext from '@/page/Workspace/context/ResourceTreeContext'; -import { toInteger } from 'lodash'; const useDataSourceDrawer = () => { - const [addDSVisiable, setAddDSVisiable] = useState(false); + const [dataSourceDrawerVisiable, setDataSourceDrawerVisiable] = useState(false); const [editDatasourceId, setEditDatasourceId] = useState(null); const [copyDatasourceId, setCopyDatasourceId] = useState(null); const context = useContext(ResourceTreeContext); @@ -47,8 +46,8 @@ const useDataSourceDrawer = () => { }; return { - addDSVisiable, - setAddDSVisiable, + dataSourceDrawerVisiable, + setDataSourceDrawerVisiable, editDatasourceId, setEditDatasourceId, copyDatasourceId, diff --git a/src/page/Workspace/SideBar/ResourceTree/index.tsx b/src/page/Workspace/SideBar/ResourceTree/index.tsx index 1becd1372..c56c2f555 100644 --- a/src/page/Workspace/SideBar/ResourceTree/index.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/index.tsx @@ -92,8 +92,8 @@ const ResourceTree: React.FC = function ({ const { expandedKeys, loadedKeys, sessionIds, setSessionId, onExpand, onLoad, setExpandedKeys } = useTreeState(stateId); const { - addDSVisiable, - setAddDSVisiable, + dataSourceDrawerVisiable, + setDataSourceDrawerVisiable, editDatasourceId, setEditDatasourceId, copyDatasourceId, @@ -391,7 +391,7 @@ const ResourceTree: React.FC = function ({ node={node} setCopyDatasourceId={setCopyDatasourceId} deleteDataSource={deleteDataSource} - setAddDSVisiable={setAddDSVisiable} + setDataSourceDrawerVisiable={setDataSourceDrawerVisiable} setEditDatasourceId={setEditDatasourceId} copyDatasourceId={copyDatasourceId} reload={reload} @@ -495,22 +495,13 @@ const ResourceTree: React.FC = function ({
{ setEditDatasourceId(null); - setAddDSVisiable(false); - }} - onSuccess={dataSourceChangeReload} - /> - - { + setDataSourceDrawerVisiable(false); setCopyDatasourceId(null); }} onSuccess={dataSourceChangeReload} From 760661e28dfd0dc002868440bcbf3e88ba1db6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Thu, 21 Aug 2025 09:45:48 +0800 Subject: [PATCH 027/239] PullRequest: 940 feat/NL2SQL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/NL2SQL of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/940 Reviewed-by: 晓康 * feat: chatInline 相关代码添加 * feat: inlineChat 使用 * feat: 获取AI Enabled * feat: 自动补全 * feat: AI 代码补全相关组件 * feat: 展示 AI 补全按钮 * refactor: 暂时注释 * feat: 模型选择 * feat: 模型选择无框样式 * feat: tooltip * feat: 样式调整 * feat: 代码更新 * feat: 暗提示添加 * feat: 调整菜单项名称 * feat: 提示语添加 * feat: 路由跳转 * feat: 个人空间适配 * refactor: 补充返回 * feat: 模型不可用提示 * refactor: 属性更新 * refactor: 提取工具函数 * refactor: 样式名称调整 * feat: modifySync改成使用SSE * feat: 移除多云的埋点代码 * refactor: 删除AIPlan * feat: inlineChat 展示模型列表 * feat: 样式调整 * feat: 设置模型选择初始值 * refactor: 样式提取到 less 文件中 * refactor: 删除多余代码 * feat: 调整交互逻辑 * feat: 接收SSE POST 接口的数据 * feat: post 请求参数传递 * feat: 拼接 SSE 返回内容 * feat: 修改接口名称 * refactor: 删除未使用的接口 * feat: 自动补全sql相关代码 * feat: 自动补全SQL使用 SSE 接口 * feat: 自动补全 sql 使用llm 默认 model * feat: aiConfig 提取到setting * feat: 补充计数 * fix: 修复补全接口调用 * feat: 补充model参数 * feat: modifySync 接口参数补充 * feat: sse 返回值状态添加 * feat: 错误返回使用 message 提示 * feat: 隐藏添加到对话 * refactor: 删除AI生成的国际化token * feat: model 请求改为手动发 * fix: 模型加载loading 正常展示 * feat: 调整暗提示颜色 * feat: 打开菜单时隐藏ai widget * feat: tooltip 跟 model 选择面板不同时出现 * feat: model 内容添加供应商 * feat: 修改不可用判定逻辑 * feat: 补充 cursorPosition * feat: 隐藏chatBox 相关代码 * feat:提示语优化 * feat: 模型调整 * feat: SQL拼接调整 * feat: 删除多余代码 * refactor: 优化 TS 定义名称 * feat: models 数据存在 sqlPage * feat: 调整 aiEnabled * feat: largeModel 交互 * feat: aiConfig 关闭时不展示ai行内提示 * refactor: 优化代码实现 * feat: 智能补全使用 AIConfig 控制 * refactor: 清理不存在的国际化token * feat: admin 权限判断 * refactor: 优化样式 * feat: 智能补全关闭时不展示按钮 * refactor: 移除不存在的国际化 token * feat: selectedModel 默认值 * fix: 加载中InlineChat 变黑问题 --- package.json | 1 + pnpm-lock.yaml | 24 +- src/common/network/ai.ts | 160 ++++ src/common/network/chat.ts | 83 +++ src/component/AICompletionState/index.less | 26 + src/component/AICompletionState/index.tsx | 50 ++ src/component/AIState/AI_Loading.json | 685 ++++++++++++++++++ src/component/AIState/index.less | 26 + src/component/AIState/index.tsx | 65 ++ .../CommonTable/component/FilterContent.tsx | 5 +- .../EditorToolBar/actions/script.tsx | 8 + src/component/EditorToolBar/config.ts | 1 + src/component/MonacoEditor/index.tsx | 2 + .../plugins/ob-language/service.ts | 48 ++ src/component/Toolbar/index.tsx | 1 + src/d.ts/ai.ts | 8 + src/d.ts/chat.ts | 161 ++++ src/d.ts/datasource.ts | 9 + src/d.ts/llm.ts | 7 +- .../LargeModel/component/EditModal/index.tsx | 4 +- .../component/ModelSelect/index.less | 1 + .../component/ModelSelect/index.tsx | 8 +- .../ExternalIntegration/LargeModel/index.tsx | 164 ++--- .../components/SQLPage/InlineChat/index.less | 103 +++ .../components/SQLPage/InlineChat/index.tsx | 554 ++++++++++++++ .../components/SQLPage/InlineChat/util.tsx | 684 +++++++++++++++++ .../Workspace/components/SQLPage/index.tsx | 127 +++- src/store/copilot.ts | 592 +++++++++++++++ src/store/login.ts | 1 + src/store/setting.ts | 61 +- src/svgr/AIChat_filled.svg | 16 + src/svgr/AI_Format.svg | 22 + src/svgr/ai_disable.svg | 14 + src/svgr/ai_enable.svg | 40 + src/svgr/inlinecomplete_disabled.svg | 20 + src/svgr/inlinecomplete_enabled.svg | 26 + src/util/request/largeModel.ts | 8 +- 37 files changed, 3677 insertions(+), 138 deletions(-) create mode 100644 src/common/network/ai.ts create mode 100644 src/common/network/chat.ts create mode 100644 src/component/AICompletionState/index.less create mode 100644 src/component/AICompletionState/index.tsx create mode 100644 src/component/AIState/AI_Loading.json create mode 100644 src/component/AIState/index.less create mode 100644 src/component/AIState/index.tsx create mode 100644 src/d.ts/ai.ts create mode 100644 src/d.ts/chat.ts create mode 100644 src/page/Workspace/components/SQLPage/InlineChat/index.less create mode 100644 src/page/Workspace/components/SQLPage/InlineChat/index.tsx create mode 100644 src/page/Workspace/components/SQLPage/InlineChat/util.tsx create mode 100644 src/store/copilot.ts create mode 100644 src/svgr/AIChat_filled.svg create mode 100644 src/svgr/AI_Format.svg create mode 100644 src/svgr/ai_disable.svg create mode 100644 src/svgr/ai_enable.svg create mode 100644 src/svgr/inlinecomplete_disabled.svg create mode 100644 src/svgr/inlinecomplete_enabled.svg diff --git a/package.json b/package.json index 7d1c2d913..d6476cc11 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,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/pnpm-lock.yaml b/pnpm-lock.yaml index 88b10ecb9..d602fe2a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -210,6 +210,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 @@ -6681,6 +6684,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==} @@ -13177,7 +13189,7 @@ snapshots: '@types/history@5.0.0': dependencies: - history: 4.10.1 + history: 5.3.0 '@types/hoist-non-react-statics@3.3.7(@types/react@16.14.65)': dependencies: @@ -13262,7 +13274,7 @@ snapshots: '@types/history': 4.7.11 '@types/react': 16.14.65 '@types/react-router': 5.1.20 - redux: 3.7.2 + redux: 4.2.1 '@types/react-router@5.1.20': dependencies: @@ -18655,6 +18667,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 diff --git a/src/common/network/ai.ts b/src/common/network/ai.ts new file mode 100644 index 000000000..75930813a --- /dev/null +++ b/src/common/network/ai.ts @@ -0,0 +1,160 @@ +/* + * 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 } from '@/d.ts/ai'; +import login from '@/store/login'; +import request from '@/util/request'; +import { message } from 'antd'; +interface IModifySyncProps { + input: string; + fileName: string; + fileContent: string; + databaseId: number; + startPosition?: number; + endPosition?: number; + cursorPosition?: number; + questionType: AIQuestionType; + model: string; + stream?: boolean; + sid: string; +} + +enum ESseEventStatus { + IN_PROGRESS = 'IN_PROGRESS', + COMPLETED = 'COMPLETED', + FAILED = 'FAILED', +} + +async function fetchPostSSE(url, data, options = {}) { + const controller = new AbortController(); + let accumulatedContent = ''; + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'text/event-stream', + 'Content-Type': 'application/json', + // ...options.headers + }, + body: JSON.stringify(data), + signal: controller.signal, + ...options, + }); + + if (!response.ok || !response.body) { + throw new Error(`SSE connection failed: ${response.status}`); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const events = buffer.split('\n\n'); + buffer = events.pop() || ''; + + for (const event of events) { + if (event.trim()) { + const eventData = processSSEEvent(event); + if (eventData && eventData.data) { + accumulatedContent += eventData.data; + } + } + } + } + } catch (error) { + if (error.name !== 'AbortError') { + console.error('SSE Error:', error); + } + } + + return { + close: () => controller.abort(), + getAccumulatedContent: () => accumulatedContent, + }; +} + +function processSSEEvent(rawEvent) { + const event = { data: '', type: 'message', id: null }; + + for (const line of rawEvent.split('\n')) { + const [field, ...valueParts] = line.split(':'); + const value = JSON.parse(valueParts.join(':').trim()); + if (value.status === ESseEventStatus.FAILED) { + message.error(value.errorMessage); + return; + } + if (value.status === ESseEventStatus.COMPLETED && !value.content) { + return; + } + switch (field) { + case 'event': + event.type = value; + break; + case 'data': + event.data = value.content; + break; + case 'id': + event.id = value; + break; + // 可以处理其他字段如 retry + } + } + + return event; +} + +// 关闭连接 +// connection.close(); +export async function modifySync({ + input, + fileName, + fileContent, + databaseId, + startPosition, + endPosition, + questionType, + model, + cursorPosition, + stream = true, + sid, +}: IModifySyncProps): Promise { + if (!model) return; + const connection = await fetchPostSSE( + `/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/common/network/chat.ts b/src/common/network/chat.ts new file mode 100644 index 000000000..54587acb6 --- /dev/null +++ b/src/common/network/chat.ts @@ -0,0 +1,83 @@ +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/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..32b5ca37d --- /dev/null +++ b/src/component/AICompletionState/index.tsx @@ -0,0 +1,50 @@ +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..a89a14587 --- /dev/null +++ b/src/component/AIState/index.tsx @@ -0,0 +1,65 @@ +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/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}} { + setIsShowModelSelect(open); + }} + popupRender={(menu) => ( +
+ {/* 搜索框 */} +
+ setSearchValue(e.target.value)} + allowClear + /> +
+ {menu} +
+ )} + placeholder="请选择模型" + style={{ width: 230 }} + options={selectOptions} + loading={modelsLoading} + showSearch={false} + filterOption={false} + /> + + {selectedModel && !isSelectedModelAvailable && ( + + + + )} + + + AI 助手生成内容的准确性和完整性无法保证,仅供参考 + + + ); + }; + + const renderLargeModelTip = (type?: string) => { + const icon = type && ; + if (allModels?.length > 0) { + return null; + } + if (isAdmin) { + return ( +
+ {icon} + + 暂无可用模型,请前往 + { + if (login.isPrivateSpace()) { + message.warning('请前往团队空间'); + return; + } + history.push('/externalIntegration/approval'); + }} + > + 外部集成>大模型集成 + {' '} + 进行模型设置 + +
+ ); + } else { + return ( +
+ {icon} + 暂无可用模型,请联系系统管理员进行模型设置 +
+ ); + } + }; + function renderTip() { + if (applied) { + return ( + + + + + ); + } + if (!loading) { + if (allModels?.length === 0 && !modelsLoading) { + return renderLargeModelTip('info'); + } else { + return renderLargeModelSelect(); + } + } + return ( + + AI 生成中... + + ); + } + function getModeTag() { + switch (mode) { + case AIQuestionType.SQL_OPTIMIZER: { + return ( + + SQL 优化 + + ); + } + case AIQuestionType.SQL_DEBUGGING: { + return ( + + SQL 纠错 + + ); + } + case AIQuestionType.SQL_MODIFIER: { + return ( + + SQL 改写 + + ); + } + case AIQuestionType.NL_2_SQL: { + return ( + + SQL 生成 + + ); + } + default: { + return null; + } + } + } + return ( + +
+ + { + if (open) { + return; + } + setIsShowMode(open); + }} + destroyOnHidden + autoFocus + menu={{ + autoFocus: true, + activeKey: mode, + onClick(info) { + if (!mode) { + setValue(''); + } + setMode(info.key as AIQuestionType); + setIsShowMode(false); + inputRef.current?.focus(); + }, + items: [ + { + type: 'group', + key: 'instruction', + label: '指令', + children: [ + { + label: 'SQL 生成', + key: AIQuestionType.NL_2_SQL, + onClick() { + console.log('click n2 sql'); + }, + }, + { + label: 'SQL 纠错', + key: AIQuestionType.SQL_DEBUGGING, + }, + { + label: 'SQL 改写', + key: AIQuestionType.SQL_MODIFIER, + }, + { + label: 'SQL 优化', + key: AIQuestionType.SQL_OPTIMIZER, + }, + ], + }, + ], + }} + > + { + setIsShowMode(true); + }} + > + {getModeTag()} + + } + ref={inputRef} + suffix={renderInputAction()} + value={value} + onChange={(e) => { + if (!value && e.target.value && !mode && e.target.value === '/') { + setIsShowMode(true); + } + setValue(e.target.value); + }} + placeholder="请输入消息" + onCompositionStart={() => setLock(true)} + onCompositionEnd={() => setLock(false)} + onPressEnter={(e) => { + if (!applied && !loading && value && !lock) { + send(); + } + }} + onKeyDown={(e) => { + if (e.key === 'Escape') { + if (loading) { + cancel(); + } + dispose(); + } + if (lock) { + return; + } + if (e.key === 'Delete' && mode) { + setMode(undefined); + } else if (e.key === 'Backspace' && !value) { + setMode(undefined); + } + }} + /> + + + +
+ + {renderTip()} +
+ ); +} diff --git a/src/page/Workspace/components/SQLPage/InlineChat/util.tsx b/src/page/Workspace/components/SQLPage/InlineChat/util.tsx new file mode 100644 index 000000000..347ddba8b --- /dev/null +++ b/src/page/Workspace/components/SQLPage/InlineChat/util.tsx @@ -0,0 +1,684 @@ +import { modifySync } from '@/common/network/ai'; +import { IEditor, IFullEditor } from '@/component/MonacoEditor'; +import SessionStore from '@/store/sessionManager/session'; +import setting from '@/store/setting'; +import { ReactComponent as AIIcon } from '@/svgr/ai_enable.svg'; + +import { generateUniqKey } from '@/util/utils'; +import { clone } from 'lodash'; +import { autorun, reaction } from 'mobx'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { render, unmountComponentAtNode } from 'react-dom'; +import InlineChat from '.'; +import { getKeyCodeText } from '@/component/Input/Keymap/keycodemap'; +import copilotStore from '@/store/copilot'; +import { message } from 'antd'; +import { AIQuestionType } from '@/d.ts/ai'; +import Icon from '@ant-design/icons'; + +interface IStore { + mode: AIQuestionType; +} + +const defaultStore: IStore = { + mode: AIQuestionType.SQL_MODIFIER, +}; + +function resetStore(store: IStore) { + Object.keys(defaultStore).forEach((key) => { + store[key] = defaultStore[key]; + }); + return store; +} + +export function createStore(): IStore { + const store = clone(defaultStore); + return store; +} + +export function addAIHint(editor: IEditor) { + const hintText = '⌘+K 唤起 AI 内嵌对话'; + + const hintWidget = { + domNode: null, + position: null, + getId: function () { + return 'ai.hint.widget'; + }, + getDomNode: function () { + if (!this.domNode) { + this.domNode = document.createElement('div'); + this.domNode.style.width = 'max-content'; + this.domNode.textContent = hintText; + this.domNode.style.color = '#C1CBE0'; + this.domNode.style.pointerEvents = 'none'; + this.domNode.style.fontStyle = 'italic'; + } + return this.domNode; + }, + getPosition: function () { + return this.position; + }, + }; + + let isWidgetVisible = false; + + const updateHint = () => { + const shouldShowHint = setting.AIConfig?.copilotEnabled; + + if (!shouldShowHint) { + if (isWidgetVisible) { + editor.removeContentWidget(hintWidget); + isWidgetVisible = false; + } + return; + } + + const position = editor.getPosition(); + const model = editor.getModel(); + + if (!position || !model || !editor.hasTextFocus()) { + if (isWidgetVisible) { + editor.removeContentWidget(hintWidget); + isWidgetVisible = false; + } + return; + } + + const lineContent = model.getLineContent(position.lineNumber); + + if (lineContent.trim() === '') { + const newPosition = { + position: { lineNumber: position.lineNumber, column: 1 }, + preference: [monaco.editor.ContentWidgetPositionPreference.EXACT], + }; + if (!isWidgetVisible) { + hintWidget.position = newPosition; + editor.addContentWidget(hintWidget); + isWidgetVisible = true; + } else { + const widgetPosition = hintWidget.position.position; + if (widgetPosition.lineNumber !== position.lineNumber) { + editor.removeContentWidget(hintWidget); + hintWidget.position = newPosition; + editor.addContentWidget(hintWidget); + } + } + } else { + if (isWidgetVisible) { + editor.removeContentWidget(hintWidget); + isWidgetVisible = false; + } + } + }; + + updateHint(); + + const disposables = [ + editor.onDidChangeCursorPosition(updateHint), + editor.onDidFocusEditorWidget(updateHint), + editor.onDidBlurEditorWidget(updateHint), + editor.onDidChangeModelContent(updateHint), + // 响应 AI 配置变化 + reaction( + () => ({ + aiEnabled: setting.AIEnabled, + copilotEnabled: setting.AIConfig?.copilotEnabled, + }), + updateHint, + ), + ]; + + return () => { + if (isWidgetVisible) { + editor.removeContentWidget(hintWidget); + } + disposables.forEach((d) => { + if (typeof d === 'function') { + d(); // reaction 返回的是函数 + } else { + d.dispose(); // editor 事件返回的是对象 + } + }); + }; +} + +export function addAIContextMenu( + editor: IEditor, + store: IStore, + showInlineChat: () => void, + fullEditor: IFullEditor, + getSession: () => SessionStore, + hideIcon: () => void, +) { + const selectionContext = editor.createContextKey('selectionContext', false); + const aiContext = editor.createContextKey('aiContext', false); + const dispose = autorun(() => { + if (setting.AIEnabled) { + aiContext.set(true); + } else { + aiContext.set(false); + } + }); + editor.onDidChangeCursorSelection((e) => { + const selection = e.selection; + if (selection.isEmpty()) { + selectionContext.set(false); + } else { + selectionContext.set(true); + } + }); + + // 监听右键事件,在打开上下文菜单时隐藏 AI Icon + const editorDomNode = editor.getDomNode(); + + if (editorDomNode) { + editorDomNode.addEventListener('contextmenu', hideIcon); + } + editor.addAction({ + label: '🪄 AI Format Document', + id: 'ai-sql-format', + contextMenuGroupId: 'navigation', + precondition: 'aiContext', + contextMenuOrder: 530, + keybindingContext: 'aiContext', + keybindings: [], + async run() { + const range = editor.getModel().getFullModelRange(); + const sql = editor.getModel().getValueInRange(range); + if (!sql?.trim()) { + return; + } + const session = getSession(); + const formattedSQL = await modifySync({ + input: '', + fileName: '', + fileContent: sql, + databaseId: session?.odcDatabase?.id || null, + startPosition: editor.getModel().getOffsetAt(range.getStartPosition()), + endPosition: editor.getModel().getOffsetAt(range.getEndPosition()), + questionType: AIQuestionType.SQL_FORMATTING, + model: setting.AIConfig?.defaultLlmModel, + stream: true, + sid: session?.sessionId || '', + }); + if (!formattedSQL) { + return; + } + const op = { + identifier: { + major: 1, + minor: 1, + }, + + range, + text: formattedSQL, + forceMoveMarkers: true, + }; + editor.executeEdits('AI_FORMAT', [op]); + }, + }); + editor.addAction({ + label: '🪄 AI Format Selection', + id: 'ai-sql-format-selection', + contextMenuGroupId: 'navigation', + precondition: 'aiContext && selectionContext', + contextMenuOrder: 540, + keybindingContext: 'aiContext && selectionContext', + keybindings: [], + async run() { + const range = editor.getSelection(); + const sql = editor.getModel().getValueInRange(range); + if (!sql?.trim()) { + return; + } + const session = getSession(); + const formattedSQL = await modifySync({ + input: '', + fileName: '', + fileContent: sql, + databaseId: session?.odcDatabase?.id || null, + startPosition: editor.getModel().getOffsetAt(range.getStartPosition()), + endPosition: editor.getModel().getOffsetAt(range.getEndPosition()), + questionType: AIQuestionType.SQL_FORMATTING, + model: setting.AIConfig?.defaultLlmModel, + sid: session?.sessionId || '', + }); + if (!formattedSQL) { + return; + } + const op = { + identifier: { + major: 1, + minor: 1, + }, + + range, + text: formattedSQL, + forceMoveMarkers: true, + }; + editor.executeEdits('AI_FORMAT', [op]); + }, + }); + editor.addAction({ + label: '🪄 SQL 改写', + id: 'sql-optimization', + contextMenuGroupId: 'navigation', + contextMenuOrder: 520, + precondition: 'aiContext && selectionContext', + keybindingContext: 'aiContext && selectionContext', + keybindings: [], + run() { + store['mode'] = AIQuestionType.SQL_OPTIMIZER; + showInlineChat(); + }, + }); + editor.addAction({ + label: '🪄 SQL Debugging', + id: 'sql-debugging', + contextMenuOrder: 510, + contextMenuGroupId: 'navigation', + precondition: 'aiContext && selectionContext', + keybindingContext: 'aiContext && selectionContext', + keybindings: [], + run() { + store['mode'] = AIQuestionType.SQL_DEBUGGING; + showInlineChat(); + }, + }); + return () => { + dispose(); + }; +} + +function getEmptyPosition(editor: IEditor) { + /** + * 计算一个空位 + * 算法:从上下两行开始找,寻找离这个光标最近的空位 + * 假如找到距离 20 以内的,则通过,否则,找5行以内符合要求的 + * 要求预留宽度为 10 字符 + */ + const cursor = editor.getPosition(); + let minDistance = Number.MAX_SAFE_INTEGER; + let minDistancePos = { + lineNumber: cursor.lineNumber + 1, + column: cursor.column, + }; + let steps = [1, -2, 2, -3, 3, -4, 4, -5, 5]; + let step = steps.shift(); + const selection = editor.getSelection(); + while (minDistance > 20 && step) { + const line = cursor.lineNumber + step; + if (selection?.startLineNumber <= line && selection?.endLineNumber >= line) { + step = steps.shift(); + continue; + } + if (line > 0) { + const isLine1BeginEmpty = !editor + .getModel() + .getValueInRange({ + startLineNumber: line, + startColumn: 1, + endLineNumber: line, + endColumn: 10, + }) + ?.trim(); + if (isLine1BeginEmpty) { + /** + * 计算头部 + */ + const right = + line > editor.getModel().getLineCount() + ? 0 + : editor.getModel().getLineFirstNonWhitespaceColumn(line); + if (right >= cursor.column) { + const distance = Math.abs(line - cursor.lineNumber); + if (distance < minDistance) { + minDistance = distance; + minDistancePos = { + lineNumber: line, + column: Math.max(cursor.column - 9, 1), + }; + } + } else if (cursor.column > right) { + const distance = Math.sqrt( + Math.pow(Math.abs(line - cursor.lineNumber), 2) + + Math.pow(Math.abs(right - cursor.column), 2), + ); + if (distance < minDistance) { + minDistance = distance; + minDistancePos = { + lineNumber: line, + column: right - 9, + }; + } + } + } + const lineEnd = + line > editor.getModel().getLineCount() ? 0 : editor.getModel().getLineMaxColumn(line); + if (lineEnd < cursor.column) { + const distance = Math.abs(line - cursor.lineNumber); + if (distance < minDistance) { + minDistance = distance; + minDistancePos = { + lineNumber: line, + column: cursor.column + 1, + }; + } + } else if (lineEnd >= cursor.column) { + const distance = Math.sqrt( + Math.pow(Math.abs(line - cursor.lineNumber), 2) + + Math.pow(Math.abs(lineEnd - cursor.column), 2), + ); + if (distance < minDistance) { + minDistance = distance; + minDistancePos = { + lineNumber: line, + column: lineEnd + 1, + }; + } + } + } + step = steps.shift(); + } + console.log(minDistance); + return minDistancePos; +} + +export function addAIIcon( + editor: IEditor, + store: IStore, + showInlineChat: () => void, + fullEditor: IFullEditor, +): { dispose: () => void; hideIcon: () => void } { + let iconWidget; + const clearEditor = () => { + if (iconWidget) { + editor.removeContentWidget(iconWidget); + iconWidget = null; + } + }; + let root; + const disposeEditor = editor.onDidChangeCursorSelection((e) => { + clearEditor(); + if (!setting.AIConfig?.completionEnabled) { + return; + } + const selection = editor.getSelection(); + if ( + editor.getModel()?.getOffsetAt(selection?.getStartPosition()) === + editor.getModel()?.getOffsetAt(selection?.getEndPosition()) + ) { + return; + } + const dom = document.createElement('div'); + // const handleAddToConversation = () => { + // const str = editor.getSelectionContent(); + // if (str?.length <= 2000) { + // copilotStore?.toggleVisibility?.(true); + // copilotStore?.addCodeBlock?.(editor.getSelectionContent()); + // editor.removeContentWidget(iconWidget); + // iconWidget = null; + // } else { + // message.warning('选中 SQL 超过 2000 个字符,暂不支持添加到对话,请重新选择' + // ); + // } + // }; + const handleEdit = () => { + const selection = editor.getSelection(); + store.mode = monaco.Position.equals(selection.getStartPosition(), selection.getEndPosition()) + ? AIQuestionType.NL_2_SQL + : AIQuestionType.SQL_MODIFIER; + showInlineChat(); + editor.removeContentWidget(iconWidget); + iconWidget = null; + }; + iconWidget = { + domNode: (function () { + const id = generateUniqKey(); + let domNode = dom; + domNode.id = id; + return domNode; + })(), + getId: function () { + return 'inlinechat.widget'; + }, + getDomNode: function () { + return this.domNode; + }, + getPosition: function () { + const pos = getEmptyPosition(editor); + return { + position: { + lineNumber: pos.lineNumber, + column: pos.column, + }, + preference: [ + // editor.ContentWidgetPositionPreference.ABOVE, + monaco.editor.ContentWidgetPositionPreference.EXACT, + ], + }; + }, + }; + if ( + editor.getModel()?.getOffsetAt(selection?.getStartPosition()) !== + editor.getModel()?.getOffsetAt(selection?.getEndPosition()) + ) { + editor.addContentWidget(iconWidget); + render( +
+ + + + {/* */} +
+ + 编辑 + {getKeyCodeText('57,41')} + + {/* + +添加到对话 + + {getKeyCodeText('57,42')} + */} +
+
+
, + dom, + ); + root = { + unmount: () => { + unmountComponentAtNode(dom); + }, + }; + } + }); + editor.addAction({ + id: 'disable-expand-line', + label: 'Disable Expand Line Selection', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyL], + run: function () { + const str = editor.getSelectionContent(); + if (str?.length) { + copilotStore.toggleVisibility(true); + if (str?.length <= 2000) { + copilotStore?.addCodeBlock?.(str); + editor.removeContentWidget(iconWidget); + } else { + message.warning('选中 SQL 超过 2000 个字符,暂不支持添加到对话,请重新选择'); + } + } else { + copilotStore.toggleVisibility(); + } + }, + }); + function dispose() { + disposeEditor.dispose(); + root?.unmount(); + root = null; + } + + function hideIcon() { + clearEditor(); + } + + return { dispose, hideIcon }; +} +/** + * 增加ai action,唤起对话窗 + */ +export function addAIAction( + editor: IEditor, + getSession: () => SessionStore, + store: IStore, + fullEditor: IFullEditor, + modelsData?: { + allModels: any[]; + modelsLoading: boolean; + onRefreshModels?: () => void; + }, +) { + let inlineChatDispose; + async function showInlineChat() { + if (inlineChatDispose) { + inlineChatDispose(); + } + if (!setting.AIConfig?.copilotEnabled) { + return; + } + /** + * 暂停50ms,避免monaco计算bug + */ + await new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, 150); + }); + const selection = editor.getSelection(); + const begin = selection + ? Math.min(selection.startLineNumber, selection.endLineNumber) + : editor.getPosition()?.lineNumber; + let viewZoneId = null; + editor.changeViewZones(function (changeAccessor) { + let domNode = document.createElement('div'); + viewZoneId = changeAccessor.addZone({ + afterLineNumber: begin - 1, + heightInPx: 110, + domNode: domNode, + }); + }); + const dom = document.createElement('div'); + let contentWidget = { + domNode: (function () { + const id = generateUniqKey(); + let domNode = dom; + domNode.id = id; + domNode.style.height = '110px'; + return domNode; + })(), + getId: function () { + return 'my.content.widget'; + }, + getDomNode: function () { + return this.domNode; + }, + getPosition: function () { + return { + position: { + lineNumber: begin, + column: 0, + }, + preference: [monaco.editor.ContentWidgetPositionPreference.ABOVE], + }; + }, + }; + editor.addContentWidget(contentWidget); + const root = { unmount: () => {} }; + let dispose: monaco.IDisposable; + inlineChatDispose = () => { + root?.unmount?.(); + dispose?.dispose?.(); + editor.removeContentWidget(contentWidget); + editor.changeViewZones(function (changeAccessor) { + changeAccessor.removeZone(viewZoneId); + }); + inlineChatDispose = null; + }; + render( +
+ +
, + dom, + ); + root.unmount = () => { + unmountComponentAtNode(dom); + }; + dispose = editor.onKeyDown((e) => { + if (e.keyCode === 9) { + inlineChatDispose(); + } + }); + } + editor.addAction({ + id: 'ai', + label: '🪄 AI Inline Chat', + contextMenuGroupId: 'navigation', + precondition: 'aiContext', + keybindingContext: 'aiContext', + contextMenuOrder: 500, + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK], + run: async (_editor) => { + const selection = _editor.getSelection(); + store.mode = monaco.Position.equals(selection.getStartPosition(), selection.getEndPosition()) + ? AIQuestionType.NL_2_SQL + : AIQuestionType.SQL_MODIFIER; + showInlineChat(); + }, + }); + return showInlineChat; +} + +export function getDefaultValue(mode: AIQuestionType) { + switch (mode) { + case AIQuestionType.SQL_DEBUGGING: { + return '修复语法问题'; + } + case AIQuestionType.SQL_OPTIMIZER: { + return '优化SQL'; + } + default: { + return ''; + } + } +} diff --git a/src/page/Workspace/components/SQLPage/index.tsx b/src/page/Workspace/components/SQLPage/index.tsx index b70bb77f4..ae70ac8eb 100644 --- a/src/page/Workspace/components/SQLPage/index.tsx +++ b/src/page/Workspace/components/SQLPage/index.tsx @@ -23,7 +23,7 @@ import { batchGetDataModifySQL } from '@/common/network/table'; import { ProfileType } from '@/component/ExecuteSqlDetailModal/constant'; import ExecuteSQLModal from '@/component/ExecuteSQLModal'; import { getKeyCodeValue } from '@/component/Input/Keymap/keycodemap'; -import { IEditor } from '@/component/MonacoEditor'; +import { IEditor, IFullEditor } from '@/component/MonacoEditor'; import SaveSQLModal from '@/component/SaveSQLModal'; import ScriptPage from '@/component/ScriptPage'; import SQLConfigContext from '@/component/SQLConfig/SQLConfigContext'; @@ -58,6 +58,8 @@ import { formatMessage } from '@/util/intl'; import notification from '@/util/notification'; import { splitSqlForHighlight } from '@/util/sql'; import { generateAndDownloadFile, getCurrentSQL } from '@/util/utils'; +import { getModelProviders, getProviderModels } from '@/util/request/largeModel'; +import { IModel } from '@/d.ts/llm'; import { message, Spin } from 'antd'; import { debounce, isNil } from 'lodash'; import { inject, observer } from 'mobx-react'; @@ -70,6 +72,13 @@ import Trace from '../Trace'; import ExecDetail from './ExecDetail'; import ExecPlan from './ExecPlan'; import styles from './index.less'; +import { + addAIAction, + addAIContextMenu, + addAIIcon, + addAIHint, + createStore, +} from './InlineChat/util'; interface ISQLPageState { resultHeight: number; @@ -106,6 +115,11 @@ interface ISQLPageState { baseOffset: number; status: EStatus; hasExecuted: boolean; + // AI Models 相关状态 + allModels: IModel[]; + modelsLoading: boolean; + modelsLoaded: boolean; + lastModelsLoadTime: number; } interface IProps { @@ -159,10 +173,17 @@ export class SQLPage extends Component { status: null, hasExecuted: false, isSavingScript: false, + // AI Models 初始状态 + allModels: [], + modelsLoading: false, + modelsLoaded: false, + lastModelsLoadTime: 0, }; public editor: IEditor; + public fullEditor: IFullEditor; + public chartContainer: HTMLDivElement | null = null; private timer: number | undefined; @@ -171,6 +192,7 @@ export class SQLPage extends Component { private actions: IDisposable[]; private config: Partial; + private disposes: (() => void)[] = []; constructor(props) { super(props); @@ -259,6 +281,7 @@ export class SQLPage extends Component { public componentWillUnmount() { const { pageKey, sqlStore } = this.props; const session = this.getSession(); + this.disposes.forEach((d) => d()); if (this.timer) { clearInterval(this.timer); @@ -327,14 +350,114 @@ export class SQLPage extends Component { this.config = setting.configurations; }; - public handleEditorCreated = (editor: IEditor) => { + public handleEditorCreated = (editor: IEditor, fullEditor: IFullEditor) => { this.editor = editor; // 快捷键绑定 + this.fullEditor = fullEditor; this.bindEditorKeymap(); this.debounceHighlightSelectionLine(); // 编辑光标位置变化事件 this.editor.onDidChangeCursorPosition(() => { this.debounceHighlightSelectionLine(); }); + this.initAI(); + }; + + /** + * 加载模型列表 + * @param forceRefresh 是否强制刷新,忽略缓存 + */ + private loadModels = async (forceRefresh: boolean = false): Promise => { + // 检查是否需要重新加载(缓存策略:5分钟内不重复加载) + const now = Date.now(); + const cacheExpiration = 5 * 60 * 1000; // 5分钟 + const shouldUseCache = + this.state.modelsLoaded && + now - this.state.lastModelsLoadTime < cacheExpiration && + !forceRefresh; + + if (shouldUseCache) { + return; + } + + if (this.state.modelsLoading) { + return; // 避免重复加载 + } + + try { + this.setState({ modelsLoading: true }); + const providersData = await getModelProviders(); + + // 获取所有提供商的所有模型 + const allModelsPromises = (providersData || []).map(async (provider) => { + try { + const models = await getProviderModels(provider.provider); + return (models || []).map((model) => ({ + ...model, + providerName: provider.provider, + })); + } catch (error) { + console.warn(`Failed to fetch models for provider ${provider.provider}:`, error); + return []; + } + }); + + const modelsResults = await Promise.all(allModelsPromises); + const flattenedModels = modelsResults.flat(); + + this.setState({ + allModels: flattenedModels, + modelsLoaded: true, + lastModelsLoadTime: now, + modelsLoading: false, + }); + } catch (error) { + console.error('Failed to fetch providers:', error); + this.setState({ modelsLoading: false }); + } + }; + + /** + * AI 功能挂载 + */ + public initAI = async () => { + // 初始化时加载模型 + if (!this.state.modelsLoaded && !this.state.modelsLoading) { + await this.loadModels(); + } + const store = createStore(); + const modelsData = { + allModels: this.state.allModels, + modelsLoading: this.state.modelsLoading, + onRefreshModels: () => this.loadModels(true), + }; + const show = addAIAction( + this.editor, + () => this.getSession(), + store, + this.fullEditor, + modelsData, + ); + const { dispose } = addAIIcon(this.editor, store, show, this.fullEditor); + const disposeMenu = addAIContextMenu( + this.editor, + store, + show, + this.fullEditor, + () => this.getSession(), + dispose, + ); + const disposeHint = addAIHint(this.editor); + this.disposes.push(() => { + dispose(); + disposeHint(); + disposeMenu(); + this.editor.setSelection({ + startLineNumber: this.editor.getSelection()?.startLineNumber, + startColumn: this.editor.getSelection()?.startColumn, + endLineNumber: this.editor.getSelection()?.startLineNumber, + endColumn: this.editor.getSelection()?.startColumn, + }); + }); }; public handleSQLChanged = (sql: string) => { diff --git a/src/store/copilot.ts b/src/store/copilot.ts new file mode 100644 index 000000000..42fd4babc --- /dev/null +++ b/src/store/copilot.ts @@ -0,0 +1,592 @@ +import { formatMessage } from '@/util/intl'; +import { action, observable, computed, reaction, runInAction } from 'mobx'; +import { ChatStatus, ChatReq, ChatFeedbackResult, ChatConversation } from '@/d.ts/chat'; +import { generateUniqKey } from '@/util/utils'; +import { + listChatConversations, + sendChatMessage, + submitChatFeedback, + getChatOutput, + terminateChat, + ChatFeedbackReq, + getConversationMessages, +} from '@/common/network/chat'; +import { ConnectionMode } from '@/d.ts/datasource'; +import { AIQuestionType } from '@/d.ts/ai'; + +export interface ChatMessage { + id: string; + role: 'user' | 'assistant' | 'system'; + content: any; + chatId?: number; + conversationId?: string; + status?: 'loading' | 'success' | 'error' | 'terminated'; + stages?: Array<{ [key: string]: string }>; // 阶段分析过程,如[{'recognizing intent': '3s'}, {'output stram': '3s'}] + feedbackResult?: ChatFeedbackResult; + databaseId?: number; + databaseName?: string; + dialectType?: ConnectionMode; + reference?: string[]; + chatType?: AIQuestionType; +} + +/** + * 本地会话信息 + */ +interface LocalConversation { + id: string; + title: string; + createTime: number; +} + +export class CopilotStore { + /** + * Copilot 面板是否显示 + */ + @observable + public isVisible: boolean = false; + + /** + * 当前激活的会话ID,本地前端标识 + */ + @observable + public activeConversationId: string = ''; + + /** + * 所有会话列表 + */ + @observable + public conversations: Map = new Map(); + + /** + * 历史会话列表 + */ + @observable + public historyConversations: ChatConversation[] = []; + + /** + * 本地会话记录,存储前端创建但尚未发送到后端的会话 + */ + @observable + public localConversations: Map = new Map(); + + /** + * 正在发送消息 + */ + @observable + public isSendingMessage: boolean = false; + + /** + * 正在生成回复 + */ + @observable + public isGenerating: boolean = false; + + /** + * 正在加载历史会话列表 + */ + @observable + public isLoadingHistoryConversations: boolean = false; + + /** + * 正在加载历史消息 + */ + @observable + public isLoadingConversationMessages: boolean = false; + + /** + * 代码块列表 + */ + @observable + public codeBlocks: string[] = []; + + /** + * 输入框默认值 + */ + @observable + public defaultContent: string = ''; + + /** + * 输入框选择的数据库 + */ + @observable + public defaultSelectedDatabaseId: string = null; + + /** + * 指令默认值 + */ + @observable + public defaultChatType: AIQuestionType = null; + + /** + * 轮询定时器ID + */ + private pollingTimer: ReturnType | null = null; + + constructor() { + reaction( + () => this.isVisible, + (isVisible) => { + if (isVisible && !this.activeConversationId) { + this.createNewConversation(); + } + }, + ); + } + + /** + * 添加代码块 + */ + @action + public addCodeBlock = (code: string): void => { + // 去掉重复codeblock + const existingIndex = this.codeBlocks.findIndex((block) => block === code); + if (existingIndex !== -1) { + this.codeBlocks.splice(existingIndex, 1); + } + this.codeBlocks.push(code); + }; + + /** + * 更新代码块 + */ + @action + public updateCodeBlock = (codeblocks): void => { + this.codeBlocks = codeblocks; + }; + + /** + * 清空所有代码块 + */ + @action + public clearCodeBlocks = (): void => { + this.codeBlocks = []; + }; + + /** + * 设置默认的输入内容 + */ + @action + public updateDefaultContent = (code: string): void => { + this.defaultContent = code; + }; + + /** + * 设置默认选中的数据库 + */ + @action + public updateDefaultSelectedDatabaseId = (id: string): void => { + this.defaultSelectedDatabaseId = id; + }; + + /** + * 设置默认选中的指令 + */ + @action + public updateDefaultChatType = (type: AIQuestionType | null): void => { + this.defaultChatType = type; + }; + + /** + * 清除状态 + */ + @action + public clearState = () => { + this.updateDefaultContent(null); + this.updateDefaultSelectedDatabaseId(null); + this.updateDefaultChatType(null); + this.clearCodeBlocks(); + }; + /** + * 切换Copilot面板显示状态 + */ + @action + public toggleVisibility = (visible?: boolean): void => { + if (visible) { + this.isVisible = visible; + return; + } + this.isVisible = !this.isVisible; + }; + + /** + * 创建新会话 + */ + @action + public createNewConversation = (): void => { + const conversationId = generateUniqKey('conversation'); + this.activeConversationId = conversationId; + this.conversations.set(conversationId, []); + + // 将新会话添加到本地会话记录 + this.localConversations.set(conversationId, { + id: conversationId, + title: formatMessage({ id: 'src.store.FD92E696', defaultMessage: '新会话' }), + createTime: Date.now(), + }); + }; + + /** + * 加载历史会话列表 + */ + @action + public loadHistoryConversations = async (): Promise => { + try { + this.isLoadingHistoryConversations = true; + const conversations = await listChatConversations(); + const localConversationsArray: ChatConversation[] = Array.from( + this.localConversations.values(), + ); + runInAction(() => { + // 合并后端会话和本地会话 + this.historyConversations = [...localConversationsArray, ...conversations]; + this.isLoadingHistoryConversations = false; + }); + } catch (error) { + console.error('Failed to Load History:', error); + runInAction(() => { + this.isLoadingHistoryConversations = false; + }); + } + }; + + /** + * 获取并转换会话详情 + */ + private async getAndTransformConversationMessages( + conversationId: string, + needLoading: boolean = true, + ): Promise { + // 检查是否为本地会话,且没有后端ID + const localConv = this.localConversations.get(conversationId); + if (localConv) { + return this.conversations.get(conversationId) || []; + } + if (needLoading) { + this.isLoadingConversationMessages = true; + } + // 否则从后端获取消息 + const chatHistory = await getConversationMessages(conversationId); + const messages: ChatMessage[] = []; + chatHistory.forEach((chat) => { + // 用户消息 + const userMsg: ChatMessage = { + id: `${chat.id?.toString?.()}-user`, + role: 'user', + content: chat.input, + chatId: chat.id, + conversationId: chat.conversationId, + status: 'success', + databaseId: chat.databaseId, + databaseName: chat.databaseName, + dialectType: chat.dialectType, + reference: chat?.reference, + chatType: chat?.chatType, + }; + messages.push(userMsg); + // ai回复 + const assistantMsg: ChatMessage = { + id: `${chat.id?.toString?.()}-assistant`, + role: 'assistant', + content: chat.output || '', + chatId: chat.id, + conversationId: chat.conversationId, + status: this.getStatusLabel(chat.status), + feedbackResult: chat.feedbackResult, + stages: chat.stages, + databaseId: chat.databaseId, + databaseName: chat.databaseName, + dialectType: chat.dialectType, + }; + messages.push(assistantMsg); + }); + this.isLoadingConversationMessages = false; + return messages; + } + + /** + * 选择历史会话 + */ + @action + public selectHistoryConversation = async (conversationId: string): Promise => { + if (this.activeConversationId && this.activeConversationId !== conversationId) { + this.conversations.delete(this.activeConversationId); + } + + this.activeConversationId = conversationId; + try { + const messages = await this.getAndTransformConversationMessages(conversationId); + runInAction(() => { + this.conversations.set(conversationId, messages); + }); + } catch (error) { + console.error('Failed To Load History:', error); + } + }; + + /** + * 添加消息到当前会话 + */ + @action + public addMessageToActiveConversation = (message: ChatMessage): void => { + if (!this.activeConversationId) return; + + const messages = this.conversations.get(this.activeConversationId) || []; + messages.push(message); + this.conversations.set(this.activeConversationId, messages); + }; + + /** + * 发送用户消息 + */ + @action + public sendMessage = async ( + input: string, + chatType?: AIQuestionType, + databaseId?: number, + databaseName?: string, + dialectType?: ConnectionMode, + sessionId?: string, + reference?: string[], + ): Promise => { + if (!this.activeConversationId || !input.trim()) return; + this.isSendingMessage = true; + + const userMessageId = generateUniqKey('user-msg'); + const userMessage: ChatMessage = { + id: userMessageId, + role: 'user', + content: input, + status: 'loading', + reference, + databaseName, + databaseId, + chatType, + dialectType, + }; + this.addMessageToActiveConversation(userMessage); + + const chatReq: ChatReq = { + input, + chatType: chatType, + conversationId: this.getBackendConversationId(), + databaseId, + databaseName, + dialectType, + sessionId, + reference, + obCloudOrganizationName: window._odc_params?.getOrgProjectName?.()?.obCloudOrganizationName, + obCloudProjectName: window._odc_params?.getOrgProjectName?.()?.obCloudProjectName, + }; + + const response = await sendChatMessage(chatReq); + if (response) { + runInAction(() => { + const messages = this.conversations.get(this.activeConversationId) || []; + const userMsgIndex = messages.findIndex((msg) => msg.id === userMessageId); + if (userMsgIndex >= 0) { + messages[userMsgIndex].chatId = response.id; + messages[userMsgIndex].id = `${response.id}-user`; + messages[userMsgIndex].conversationId = response.conversationId; + messages[userMsgIndex].status = 'success'; + } + + // 更新当前会话ID为后端返回的会话ID + if (this.localConversations.get(this.activeConversationId)) { + this.localConversations.delete(this.activeConversationId); + this.conversations.delete(this.activeConversationId); + this.loadHistoryConversations(); + } + this.activeConversationId = response.conversationId; + + const assistantMessage: ChatMessage = { + id: response.id?.toString?.(), + role: 'assistant', + content: '', + chatId: response.id, + conversationId: response.conversationId, + status: 'loading', + databaseId, + databaseName, + dialectType, + }; + this.conversations.set(response.conversationId, [...messages, assistantMessage]); + this.startPolling(response.id as number, response.id?.toString?.()); + this.isSendingMessage = false; + this.isGenerating = true; + }); + } else { + runInAction(() => { + const messages = this.conversations.get(this.activeConversationId) || []; + const userMsgIndex = messages.findIndex((msg) => msg.id === userMessageId); + if (userMsgIndex >= 0) { + messages[userMsgIndex].status = 'error'; + } + this.isSendingMessage = false; + this.isGenerating = false; + }); + } + }; + + /** + * 开始轮询获取回复 + */ + private startPolling = (chatId: number, messageId: string): void => { + if (this.pollingTimer) { + clearInterval(this.pollingTimer); + } + let accumulatedContent = ''; + this.pollingTimer = setInterval(async () => { + try { + const chat = await getChatOutput(chatId); + + runInAction(() => { + if (!this.activeConversationId) return; + + const messages = this.conversations.get(this.activeConversationId) || []; + const msgIndex = messages.findIndex((msg) => msg.id === messageId); + + if (msgIndex >= 0) { + if (chat.output && chat.output.trim() !== '') { + accumulatedContent += chat.output; + } + messages[msgIndex].content = accumulatedContent; + if (chat.stages) { + messages[msgIndex].stages = chat.stages; + } + messages[msgIndex].status = this.getStatusLabel(chat.status); + if (chat.status !== ChatStatus.IN_PROGRESS) { + this.stopPolling(); + this.isGenerating = false; + } + } + }); + } catch (error) { + console.error('Failed To Get AIMessage:', error); + this.stopPolling(); + runInAction(() => { + this.isGenerating = false; + }); + } + }, 500); + }; + + /** + * 停止轮询 + */ + @action + public stopPolling = (): void => { + if (this.pollingTimer) { + clearInterval(this.pollingTimer); + this.pollingTimer = null; + } + this.isGenerating = false; + }; + + /** + * 停止生成回复 + */ + @action + public stopGenerating = async (): Promise => { + if (!this.activeConversationId || !this.isGenerating) return; + + const messages = this.conversations.get(this.activeConversationId) || []; + const assistantMsg = messages.find( + (msg) => msg.role === 'assistant' && msg.status === 'loading', + ); + + if (assistantMsg && assistantMsg.chatId) { + try { + await terminateChat(assistantMsg.chatId); + } catch (error) { + console.error('Failed To Stop:', error); + } + } + }; + + /** + * 获取后端会话ID + * 如果当前会话没有后端会话ID,则返回undefined + */ + private getBackendConversationId = (): string | undefined => { + if (!this.activeConversationId) return undefined; + const messages = this.conversations.get(this.activeConversationId) || []; + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].conversationId) { + return messages[i].conversationId; + } + } + + return undefined; + }; + + private getStatusLabel = (status: ChatStatus): 'success' | 'error' | 'terminated' | 'loading' => { + const map: Record = { + [ChatStatus.COMPLETED]: 'success', + [ChatStatus.FAILED]: 'error', + [ChatStatus.CANCELED]: 'terminated', + [ChatStatus.IN_PROGRESS]: 'loading', + }; + + return map[status] as 'loading' | 'success' | 'error' | 'terminated'; + }; + + /** + * 提交反馈 + */ + @action + public submitFeedback = async ( + chatId: number, + result: ChatFeedbackResult, + content?: string, + ): Promise => { + try { + const feedbackReq: ChatFeedbackReq = { + chatId, + feedbackResult: result, + feedbackContent: content, + }; + await submitChatFeedback(chatId, feedbackReq); + + // 重新获取会话详情 + if (this.activeConversationId) { + const messages = await this.getAndTransformConversationMessages( + this.activeConversationId, + false, + ); + runInAction(() => { + this.conversations.set(this.activeConversationId, messages); + }); + } + } catch (error) { + console.error(error); + } + }; + + /** + * 检查会话是否为最新会话 + */ + public isLatestConversation = (conversationId: string): boolean => { + if (this.localConversations.get(conversationId)) { + return true; + } + const sortedHistoryConversations = [...this.historyConversations].sort( + (a, b) => Number(b.createTime) - Number(a.createTime), + ); + + return ( + sortedHistoryConversations.length > 0 && sortedHistoryConversations[0].id === conversationId + ); + }; + + /** + * 获取当前活动会话的消息 + */ + @computed + public get activeConversationMessages(): ChatMessage[] { + return this.activeConversationId ? this.conversations.get(this.activeConversationId) || [] : []; + } +} + +const copilotStore = new CopilotStore(); +export default copilotStore; diff --git a/src/store/login.ts b/src/store/login.ts index 037a48844..89517f00a 100644 --- a/src/store/login.ts +++ b/src/store/login.ts @@ -235,6 +235,7 @@ export class UserStore { initTracert(); } await setting.getSystemConfig(); + await setting.getAIConfig(); this.addLogoutListener(); } return !!user; diff --git a/src/store/setting.ts b/src/store/setting.ts index 49338e629..89292891b 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -26,8 +26,10 @@ import { isClient } from '@/util/env'; import request from '@/util/request'; import { isLinux, isWin64, kbToMb } from '@/util/utils'; import { message } from 'antd'; -import { action, observable } from 'mobx'; +import { action, observable, computed } from 'mobx'; import login, { sessionKey } from '@/store/login'; +import { IAIConfig } from '@/d.ts/llm'; +import { getAIConfig, updateAIConfig } from '@/util/request/largeModel'; export const themeKey = 'odc-theme'; const SPACE_CONFIG_EXPIRES = 60 * 1000; @@ -97,6 +99,23 @@ export class SettingStore { @observable public enableDataExport: boolean = false; + /** + * NL2SQL + */ + @observable + public enableAIInlineCompletion: boolean = true; + + @observable + public AIConfig: IAIConfig; + + @computed + public get AIEnabled() { + return this.AIConfig?.copilotEnabled || this.AIConfig?.completionEnabled || false; + } + + @observable + public isAIThinking: boolean = false; + /** * 多库变更 */ @@ -334,14 +353,44 @@ export class SettingStore { } @action - public async getUserConfig() { - const res = await request.get('/api/v2/config/users/me/configurations'); - if (res?.data) { - const config = res?.data?.contents?.reduce((data, item) => { + public disableAI() { + localStorage.setItem('odc.enableAIInlineCompletion', 'false'); + this.enableAIInlineCompletion = false; + } + @action + public enableAI() { + localStorage.setItem('odc.enableAIInlineCompletion', 'true'); + this.enableAIInlineCompletion = true; + } + @action async getAIConfig() { + const res = await getAIConfig(); + this.AIConfig = res; + } + + @action async updateAIConfig(data) { + try { + const res = await updateAIConfig(data); + if (res) { + this.AIConfig = data; + } + } catch { + console.log('fail to update ai config'); + } + } + + @action + public async getUserConfig(initData?: any) { + this.enableAIInlineCompletion = + !localStorage.getItem('odc.enableAIInlineCompletion') || + localStorage.getItem('odc.enableAIInlineCompletion') === 'true'; + const data = initData + ? initData + : (await request.get('/api/v2/config/users/me/configurations'))?.data?.contents; + if (data) { + this.configurations = data?.reduce((data, item) => { data[item.key] = item.value; return data; }, {}); - this.configurations = config; this.theme = themeConfig[this.configurations['odc.appearance.scheme']]; } else { this.configurations = {}; diff --git a/src/svgr/AIChat_filled.svg b/src/svgr/AIChat_filled.svg new file mode 100644 index 000000000..8c0b43fa2 --- /dev/null +++ b/src/svgr/AIChat_filled.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/AI_Format.svg b/src/svgr/AI_Format.svg new file mode 100644 index 000000000..b119ef7b0 --- /dev/null +++ b/src/svgr/AI_Format.svg @@ -0,0 +1,22 @@ + + + magic + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/ai_disable.svg b/src/svgr/ai_disable.svg new file mode 100644 index 000000000..06f1eb373 --- /dev/null +++ b/src/svgr/ai_disable.svg @@ -0,0 +1,14 @@ + + + 2EE678EF-DA6C-4EA2-BE3D-1E6B2CD4CEE6 + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/ai_enable.svg b/src/svgr/ai_enable.svg new file mode 100644 index 000000000..b12efa067 --- /dev/null +++ b/src/svgr/ai_enable.svg @@ -0,0 +1,40 @@ + + + 20B78FAB-48F7-48C9-9208-56BF70B8119E + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/inlinecomplete_disabled.svg b/src/svgr/inlinecomplete_disabled.svg new file mode 100644 index 000000000..d14f7db7e --- /dev/null +++ b/src/svgr/inlinecomplete_disabled.svg @@ -0,0 +1,20 @@ + + + Completion_filled + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svgr/inlinecomplete_enabled.svg b/src/svgr/inlinecomplete_enabled.svg new file mode 100644 index 000000000..dea6674e9 --- /dev/null +++ b/src/svgr/inlinecomplete_enabled.svg @@ -0,0 +1,26 @@ + + + BCFF528F-D70B-4298-9F7A-2A08864C22D1 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/util/request/largeModel.ts b/src/util/request/largeModel.ts index 7dc98fec9..c9453a423 100644 --- a/src/util/request/largeModel.ts +++ b/src/util/request/largeModel.ts @@ -17,7 +17,7 @@ import { IAIConfig, IAIConfigPayload, - IModelInfo, + IModel, IModelProvider, IProviderCredential, } from '@/d.ts/llm'; @@ -52,7 +52,7 @@ export async function getModelProviders(): Promise { /** * 获取指定供应商的模型列表 */ -export async function getProviderModels(provider: string): Promise { +export async function getProviderModels(provider: string): Promise { const result = await request.get(`/api/v2/integration/llm/providers/${provider}/models`); return result?.data?.contents; } @@ -85,7 +85,7 @@ export async function deleteModelProvider(provider: string): Promise { /** * 获取指定供应商的指定模型详情 */ -export async function getModelDetail(provider: string, modelName: string): Promise { +export async function getModelDetail(provider: string, modelName: string): Promise { const result = await request.get( `/api/v2/integration/llm/providers/${provider}/models/${modelName}`, ); @@ -105,7 +105,7 @@ export async function createProviderModel( dashscope_api_key: string; }; }, -): Promise { +): Promise { const result = await request.post(`/api/v2/integration/llm/providers/${provider}/models`, { data, }); From 9da565a168c537c5d1f3fe5f4e614ad87972d6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Tue, 26 Aug 2025 17:15:25 +0800 Subject: [PATCH 028/239] PullRequest: 917 feat/console-v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/console-v2 of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/917 Reviewed-by: 晓康 * feat: 布局调整 * fix: 展示引导图 * feat: select 添加 * feat: 统计数值展示 * feat: 已启用 * feat: chart 添加 * feat: scheduleItem 样式调整 * feat: 任务概览-布局 * feat: 工单计数器 * feat: 样式调整 * feat: 调整更多功能文本颜色 * feat: 添加自定义布局按钮 * feat: 自定义布局可拖拽受控树组件 * feat: 缓存自定义布局的设置 * feat: 自定义布局缓存key用orgId 区分不同账号 * feat: 自定义布局面板样式调整 * feat: 树不可拖拽 * feat: 只有工单、作业可拖拽 * feat: 自定义设置布局样式调整 * feat: 作业过滤器时间icon添加 * feat: 配置时间选择 * feat: 时间选择 * fix: 修复 ts 问题 * feat: 工作台二期接口添加 * feat: 使用task概览接口 * refactor: 删除多余代码 * refactor: 删除不再使用的布局控制代码 * feat: 添加task类型 * feat: 调整数据 * refactor: 删除多余代码 * fix: 修复task数据使用 * refactor: 删除多余引用 * feat: project选择 * fix: 没有选中project 则传undefined * feat: projectId 设置 * feat: title map * feat: task 用类型作key * fix: 修复 isDraggable * feat: 自定义布局控制概览展示项 * feat: 调整顶部select 展示样式 * feat: 饼图对应文本样式调整 * feat: 自定义设置控制模块展示 * feat: 饼图隐藏逻辑 * feat: 展示适配 * feat: 底部区域展示逻辑 * feat: 枚举提取 * feat: 960屏幕适配 * feat: 快速开始适配960 * fix: 任务概览展示逻辑修正 * fix: 修正select显示 * feat: 自定义布局可以即时显示在页面 * refactor: 删除多余注释 * refactor: 调整context 位置 * feat: 展示 totalCount * feat: 饼图展示数据 * refactor: 删除调试代码 * refactor: 删除饼图旧版适配样式 * refactor: 补充注释 * fix: 删除多余代码 * feat: bar图展示 * feat: 控制任务概览部分divider显示 * feat: 调整页面适配尺寸 * feat: 调整legend 位置 * feat: time 属性 * feat: 时间设置 * feat: 时间选择全部 * feat: date 持久化 * feat: 间隔展示 * feat: barChart tooltip * feat: legend 适配 960 * feat: 调整样式 * fix: 修复接口使用问题 * refactor: 优化代码 * refactor: 优化代码 * feat: quickStart 模块提取为组件 * fix: 快速开始title样式补充 * feat: 960 1个饼图展示布局 * refactor: 升级ts * feat: 接口更新 * feat: ts 定义更新 * feat: 使用todo 接口新定义 * feat: 使用todo 接口新定义 * refactor: 删除不再使用的代码 * fix: 修复projectId cache 读取 * fix: 修复selectedProjectId 初始值 * refactor: 删除多余代码 * feat: 更新chart 使用的状态key * fix: 柱状图数据展示 * feat: todo 导航 * fix: 作业数据展示 * feat: 数据使用 * fix: 状态标签与状态对应 * feat: 调整 标签顺序 * feat: 展示已启用 * fix: 已启用跳转 * fix: 跳转路由补充 --- package.json | 4 +- src/common/network/schedule.ts | 19 +- src/common/network/task.ts | 49 ++ src/component/Schedule/layout/Header/Tabs.tsx | 9 +- src/component/Task/layout/Header/Tabs.tsx | 9 +- src/d.ts/index.ts | 43 +- src/page/Console/PersonalizeLayoutContext.tsx | 36 + .../Console/components/BarChart/index.less | 79 ++ .../Console/components/BarChart/index.tsx | 150 ++++ .../Console/components/CounterCard/index.less | 21 +- .../Console/components/CounterCard/index.tsx | 26 +- .../Console/components/DonutChart/index.less | 11 +- .../Console/components/DonutChart/index.tsx | 15 +- src/page/Console/components/LabelWithIcon | 0 .../PersonalizeLayoutSetting/index.less | 28 + .../PersonalizeLayoutSetting/index.tsx | 311 ++++++++ .../Console/components/QuickStart/index.less | 121 +++ .../Console/components/QuickStart/index.tsx | 155 ++++ .../components/ScheduleCounter/index.less | 26 + .../components/ScheduleCounter/index.tsx | 21 + .../components/ScheduleItem/index.less | 23 +- .../Console/components/ScheduleItem/index.tsx | 85 +- src/page/Console/{const.ts => const.tsx} | 78 +- src/page/Console/index.less | 251 +++--- src/page/Console/index.tsx | 755 +++++++++--------- src/util/utils.ts | 14 - 26 files changed, 1715 insertions(+), 624 deletions(-) create mode 100644 src/page/Console/PersonalizeLayoutContext.tsx create mode 100644 src/page/Console/components/BarChart/index.less create mode 100644 src/page/Console/components/BarChart/index.tsx create mode 100644 src/page/Console/components/LabelWithIcon create mode 100644 src/page/Console/components/PersonalizeLayoutSetting/index.less create mode 100644 src/page/Console/components/PersonalizeLayoutSetting/index.tsx create mode 100644 src/page/Console/components/QuickStart/index.less create mode 100644 src/page/Console/components/QuickStart/index.tsx create mode 100644 src/page/Console/components/ScheduleCounter/index.less create mode 100644 src/page/Console/components/ScheduleCounter/index.tsx rename src/page/Console/{const.ts => const.tsx} (79%) diff --git a/package.json b/package.json index d6476cc11..75b591292 100644 --- a/package.json +++ b/package.json @@ -71,11 +71,10 @@ "tree-kill": "^1.2.1" }, "devDependencies": { - "axios": "^1.7.7", + "@ant-design/icons": "^4.0.0", "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", - "@ant-design/icons": "^4.0.0", "@oceanbase-odc/monaco-plugin-ob": "~1.5.3", "@oceanbase-odc/ob-intl-cli": "^2.1.3", "@oceanbase-odc/ob-parser-js": "^3.1.2", @@ -102,6 +101,7 @@ "antlr4": "~4.8.0", "array-move": "^4.0.0", "aws-sdk": "^2.1231.0", + "axios": "^1.7.7", "bignumber.js": "^9.0.0", "blueimp-md5": "^2.19.0", "cherio": "^1.0.0-rc.2", diff --git a/src/common/network/schedule.ts b/src/common/network/schedule.ts index af546c3f2..5f57b1749 100644 --- a/src/common/network/schedule.ts +++ b/src/common/network/schedule.ts @@ -12,13 +12,7 @@ import { ScheduleTaskStatus, SubTaskParameters, } from '@/d.ts/scheduleTask'; -import { - Operation, - IResponseData, - ICycleTaskStatParam, - ICycleTaskStatRecord, - CommonTaskLogType, -} from '@/d.ts'; +import { Operation, IResponseData, CommonTaskLogType, ITaskStatParam, IStat } from '@/d.ts'; import { scheduleTask, IScheduleTaskRecord } from '@/d.ts/scheduleTask'; import { ApprovalStatus } from '@/component/Schedule/interface'; import { omit } from 'lodash'; @@ -204,17 +198,16 @@ export async function getTerminateScheduleLog(terminateId: string): Promise( - params: ICycleTaskStatParam, -): Promise { - const res = await request.get('/api/v2/schedule/schedules/stats', { + params: ITaskStatParam, +): Promise> { + const res = await request.get('/api/v2/collaboration/landingPage/scheduleStat', { params, }); - return res?.data?.contents; + return res?.data; } /** diff --git a/src/common/network/task.ts b/src/common/network/task.ts index b875df2b6..2b764795e 100644 --- a/src/common/network/task.ts +++ b/src/common/network/task.ts @@ -36,7 +36,12 @@ import { TaskRecordParameters, TaskStatus, TaskType, + ITaskStatParam, + ITodos, + IGetFlowScheduleTodoParams, + IStat, IAsyncTaskResultSet, + ICycleTaskRecord, } from '@/d.ts'; import { IProject } from '@/d.ts/project'; import { EOperationType, IComparisonResultData, IStructrueComparisonDetail } from '@/d.ts/task'; @@ -171,6 +176,50 @@ export async function getUnfinishedTickets(projectId: number): Promise(params: { + connectionId?: number[]; + creator?: string; + databaseName?: string[]; + id?: number; + status?: TaskStatus[]; + type?: TaskPageType; + startTime?: number; + endTime?: number; + createdByCurrentUser?: boolean; + approveByCurrentUser?: boolean; + sort?: string; + page?: number; + size?: number; +}): Promise>> { + const res = await request.get('/api/v2/schedule/scheduleConfigs', { + params, + }); + return res?.data; +} + +/** + * 查询工单任务状态 + */ +export async function getTaskStat(params: ITaskStatParam): Promise> { + const res = await request.get('/api/v2/collaboration/landingPage/flowInstanceStat', { + params, + }); + 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: { currentOrganizationId: number; limit: number; diff --git a/src/component/Schedule/layout/Header/Tabs.tsx b/src/component/Schedule/layout/Header/Tabs.tsx index 7fee0fe93..b0605fff5 100644 --- a/src/component/Schedule/layout/Header/Tabs.tsx +++ b/src/component/Schedule/layout/Header/Tabs.tsx @@ -1,11 +1,18 @@ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { Radio } from 'antd'; import { ScheduleTab } from '@/component/Schedule/interface'; import ParamsContext from '@/component/Schedule/context/ParamsContext'; +import { useSearchParams } from '@umijs/max'; const Tabs = () => { const context = useContext(ParamsContext); const { params, setParams } = context || {}; + const [searchParams] = useSearchParams(); + const tab = searchParams.get('tab') as ScheduleTab; + + useEffect(() => { + setParams({ tab: tab || ScheduleTab.all }); + }, [tab]); const handleSelect = (e) => { setParams?.({ tab: e.target.value as ScheduleTab }); diff --git a/src/component/Task/layout/Header/Tabs.tsx b/src/component/Task/layout/Header/Tabs.tsx index e8b9e9ed4..46cc830ab 100644 --- a/src/component/Task/layout/Header/Tabs.tsx +++ b/src/component/Task/layout/Header/Tabs.tsx @@ -1,11 +1,18 @@ -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { Radio } from 'antd'; import { TaskTab } from '@/component/Task/interface'; import ParamsContext from '@/component/Task/context/ParamsContext'; +import { useSearchParams } from '@umijs/max'; const Tabs = () => { const context = useContext(ParamsContext); const { params, setParams } = context; + const [searchParams] = useSearchParams(); + const tab = searchParams.get('tab') as TaskTab; + + useEffect(() => { + setParams({ tab: tab || TaskTab.all }); + }, [tab]); const handleSelect = (e) => { setParams({ tab: e.target.value as TaskTab }); diff --git a/src/d.ts/index.ts b/src/d.ts/index.ts index 7bebbbd19..5a0d0f3dc 100644 --- a/src/d.ts/index.ts +++ b/src/d.ts/index.ts @@ -2685,9 +2685,10 @@ export interface ICycleTaskRecord { description?: string; } -export interface ICycleTaskStatParam { +export interface ITaskStatParam { currentOrganizationId: number; types: string[]; + projectId?: number; startTime?: number; endTime?: number; } @@ -2699,18 +2700,34 @@ export interface IDatabaseHistoriesParam { endTime?: number; } -export interface ICycleTaskStatRecord { - type: string; - successEnabledCount: number; - totalCount?: number; - taskStats: { - type: string; - successExecutionCount: number; - failedExecutionCount: number; - waitingExecutionCount: number; - executingCount: number; - otherCount: number; - }[]; +export interface IStat { + count: { + PENDING: number; + EXECUTING: number; + EXECUTION_FAILURE: number; + EXECUTION_SUCCESS: number; + ENABLED: number; + OTHER: number; + }; +} + +export interface ITodos { + FLOW: { + count: { + FLOW_WAIT_ME_APPROVAL: number; + FLOW_WAIT_ME_EXECUTION: number; + }; + }; + SCHEDULE: { + count: { + SCHEDULE_WAIT_ME_APPROVAL: number; + }; + }; +} + +export interface IGetFlowScheduleTodoParams { + currentOrganizatonId: number; + projectId?: number; } export enum SubTaskExecuteType { diff --git a/src/page/Console/PersonalizeLayoutContext.tsx b/src/page/Console/PersonalizeLayoutContext.tsx new file mode 100644 index 000000000..46cd269af --- /dev/null +++ b/src/page/Console/PersonalizeLayoutContext.tsx @@ -0,0 +1,36 @@ +import React, { createContext, useContext, ReactNode } from 'react'; +import { useLocalStorageState } from 'ahooks'; +import login from '@/store/login'; +import { defaultCheckedKeys } from './components/PersonalizeLayoutSetting'; + +interface PersonalizeLayoutContextType { + checkedKeys: React.Key[]; + setCheckedKeys: (keys: React.Key[]) => void; +} + +export const PersonalizeLayoutContext = createContext( + undefined, +); + +interface PersonalizeLayoutProviderProps { + children: ReactNode; +} + +export const PersonalizeLayoutProvider: React.FC = ({ + children, +}) => { + const [checkedKeys, setCheckedKeys] = useLocalStorageState( + `personalizeLayoutCheckedKeys-${login.organizationId}`, + ); + + return ( + + {children} + + ); +}; diff --git a/src/page/Console/components/BarChart/index.less b/src/page/Console/components/BarChart/index.less new file mode 100644 index 000000000..5de15f5e9 --- /dev/null +++ b/src/page/Console/components/BarChart/index.less @@ -0,0 +1,79 @@ +.bar-chart-wrapper { + width: 100%; + height: 280px; + display: flex; + align-items: center; + justify-content: center; +} + +@media (max-width: 1400px) { + .bar-chart-wrapper { + height: 240px; + } +} + +// Tooltip 样式 +.bar-chart-tooltip { + background: rgba(255, 255, 255, 0.95); + border: 1px solid #ddd; + border-radius: 4px; + padding: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + color: #132039; + + width: 160px; + + &-title { + font-size: 14px; + font-weight: 500; + margin-bottom: 8px; + padding-bottom: 6px; + &-arrow { + color: #5c6b8a; + } + } + + &-total { + font-size: 13px; + font-weight: 500; + margin-bottom: 8px; + display: flex; + align-items: center; + justify-content: space-between; + + &-number { + gap: 4px; + } + &-arrow { + color: #5c6b8a; + } + } + + &-item { + display: flex; + align-items: center; + margin: 4px 0; + font-size: 12px; + &-dot { + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 8px; + flex-shrink: 0; + } + + &-name { + flex: 1; + } + + &-value { + font-weight: 500; + margin-left: 8px; + } + + &-arrow { + margin-left: 4px; + color: #5c6b8a; + } + } +} diff --git a/src/page/Console/components/BarChart/index.tsx b/src/page/Console/components/BarChart/index.tsx new file mode 100644 index 000000000..161950ab7 --- /dev/null +++ b/src/page/Console/components/BarChart/index.tsx @@ -0,0 +1,150 @@ +import React, { useRef, useEffect, useContext, useMemo } from 'react'; +import * as echarts from 'echarts'; +import { ConsoleTextConfig, TaskTitle, TaskTypes } from '../../const'; +import './index.less'; +import { PersonalizeLayoutContext } from '@/page/Console/PersonalizeLayoutContext'; + +const BarChart = ({ data }) => { + const { status, statusColor, statusType } = ConsoleTextConfig.schdules; + const chartRef = useRef(null); + const { checkedKeys: allCheckedKeys } = useContext(PersonalizeLayoutContext); + const checkedKeys = TaskTypes.filter((item) => allCheckedKeys.includes(item)); + + useEffect(() => { + if (chartRef.current) { + const chart = echarts.init(chartRef.current); + + // 处理数据 - 使用与饼图相同的4个任务类型 + const seriesData = status.map((statusName, i) => { + return { + name: statusName, + type: 'bar', + stack: '总量', + barWidth: 20, + itemStyle: { + color: statusColor[i], + }, + data: checkedKeys.map((type) => { + return data?.[type]?.count?.[statusType[i]] || 0; + }), + }; + }); + const option = { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + formatter: function (params) { + let total = 0; + params.forEach((item) => { + total += item.value; + }); + + let result = `
`; + + // 标题 + result += `
${params[0].name}
`; + + // 任务总计 + result += + total > 0 + ? `
+ 任务总计 + ${total} > + +
` + : ''; + + // 各状态详情 + params.forEach((item) => { + if (item.value > 0) { + result += ` +
+
+ ${item.seriesName} + ${item.value} + > +
+ `; + } + }); + + result += `
`; + return result; + }, + backgroundColor: 'transparent', + borderWidth: 0, + padding: 0, + }, + grid: { + left: '3%', + right: '4%', + bottom: '15%', + top: '5%', + containLabel: true, + }, + xAxis: { + type: 'category', + data: checkedKeys.map((key) => TaskTitle[key]), + axisLabel: { + interval: 0, + fontSize: 12, + color: '#666', + formatter: function (value, index) { + if (checkedKeys.length > 8) { + return index % 2 ? '' : value; + } + return value; + }, + }, + axisTick: { + show: false, + }, + axisLine: { + lineStyle: { + color: '#e0e0e0', + }, + }, + }, + yAxis: { + type: 'value', + axisLabel: { + fontSize: 12, + color: '#666', + }, + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + splitLine: { + lineStyle: { + color: '#f0f0f0', + type: 'dashed', + }, + }, + }, + series: seriesData, + }; + + chart.setOption(option); + + // 自适应大小 + const resizeObserver = new ResizeObserver(() => { + chart.resize(); + }); + resizeObserver.observe(chartRef.current); + + return () => { + resizeObserver.disconnect(); + chart.dispose(); + }; + } + }, [data]); + + return
; +}; + +export default BarChart; diff --git a/src/page/Console/components/CounterCard/index.less b/src/page/Console/components/CounterCard/index.less index 2ac330706..364a44eff 100644 --- a/src/page/Console/components/CounterCard/index.less +++ b/src/page/Console/components/CounterCard/index.less @@ -1,15 +1,28 @@ .counterCard { display: flex; - justify-content: center; - width: 48px; - height: 50px; + justify-content: space-between; + align-items: center; + min-width: 90px; cursor: default; + height: 20px; + &:nth-child(1) { + margin-bottom: 0; + } + margin-bottom: 4px; .counter { - font-size: 18px; + cursor: pointer; + font-size: 12px; line-height: 28px; text-align: center; font-weight: 600; opacity: 0.85; + .icon { + font-size: 12px; + color: #5c6b8a; + font-weight: 400; + line-height: 20px; + margin-left: 4px; + } } .title { font-size: 12px; diff --git a/src/page/Console/components/CounterCard/index.tsx b/src/page/Console/components/CounterCard/index.tsx index 4e12cd7d5..33504f0d0 100644 --- a/src/page/Console/components/CounterCard/index.tsx +++ b/src/page/Console/components/CounterCard/index.tsx @@ -1,27 +1,23 @@ -import LabelWithIcon from '../../../../component/LabelWithIcon'; import styles from './index.less'; interface IProps { title: string; counter: number; status?: string; + onClick?: () => void; } -const CounterCard = ({ title, counter, status }: IProps) => { +const CounterCard = ({ title, counter, status, onClick }: IProps) => { return (
- 0 && status === 'failed' ? '#ff4d4f' : undefined }} - > - {counter || 0} -
- } - label={
{title || '-'}
} - gap={2} - align={['vertical', 'center']} - /> +
{title || '-'}
+
0 && status === 'failed' ? '#ff4d4f' : undefined }} + > + {counter || 0} + > +
); }; diff --git a/src/page/Console/components/DonutChart/index.less b/src/page/Console/components/DonutChart/index.less index 20a52a062..d4df895a7 100644 --- a/src/page/Console/components/DonutChart/index.less +++ b/src/page/Console/components/DonutChart/index.less @@ -1,13 +1,6 @@ .chart-wrapper { - width: 108px; - height: 108px; -} - -@media (max-width: 1130px) { - .chart-wrapper { - width: 68px; - height: 68px; - } + width: 90px; + height: 90px; } .tooltip { diff --git a/src/page/Console/components/DonutChart/index.tsx b/src/page/Console/components/DonutChart/index.tsx index 0ac69b887..1c3dc7c16 100644 --- a/src/page/Console/components/DonutChart/index.tsx +++ b/src/page/Console/components/DonutChart/index.tsx @@ -7,17 +7,20 @@ const PieChart = ({ progress }) => { const { status, statusType, statusColor } = ConsoleTextConfig.schdules; const chartRef = useRef(null); - const total = statusType.reduce( - (sum, key) => sum + (parseInt(progress?.taskStat?.[key]) || 0), - 0, - ); + const { PENDING, EXECUTING, EXECUTION_FAILURE, EXECUTION_SUCCESS, OTHER } = progress || {}; + const total = + (PENDING || 0) + + (EXECUTING || 0) + + (EXECUTION_FAILURE || 0) + + (EXECUTION_SUCCESS || 0) + + (OTHER || 0); useEffect(() => { if (chartRef.current) { const chart = echarts.init(chartRef.current); const data = status.map((name, i) => { - const count = parseInt(progress?.taskStat?.[statusType[i]]) || 0; + const count = progress?.[statusType[i]] || 0; return { name, value: count, @@ -142,7 +145,7 @@ const PieChart = ({ progress }) => { chart.dispose(); }; } - }, [progress, total, status, statusType, statusColor]); + }, [progress, total]); return
; }; diff --git a/src/page/Console/components/LabelWithIcon b/src/page/Console/components/LabelWithIcon new file mode 100644 index 000000000..e69de29bb diff --git a/src/page/Console/components/PersonalizeLayoutSetting/index.less b/src/page/Console/components/PersonalizeLayoutSetting/index.less new file mode 100644 index 000000000..413ec3540 --- /dev/null +++ b/src/page/Console/components/PersonalizeLayoutSetting/index.less @@ -0,0 +1,28 @@ +.personalizeLayoutSetting { + width: 28px; + height: 28px; + font-size: 16px; +} +.customLayoutPanel { + min-width: 200px; + :global { + .anticon { + color: #5f6b87; + } + .ant-tree-draggable-icon { + margin-right: -24px; + svg { + color: #000; + } + } + } + .panelHeader { + display: flex; + justify-content: space-between; + .panelTitle { + font-size: 12px; + line-height: 20px; + font-weight: 600; + } + } +} diff --git a/src/page/Console/components/PersonalizeLayoutSetting/index.tsx b/src/page/Console/components/PersonalizeLayoutSetting/index.tsx new file mode 100644 index 000000000..f2daed29d --- /dev/null +++ b/src/page/Console/components/PersonalizeLayoutSetting/index.tsx @@ -0,0 +1,311 @@ +import { LayoutOutlined } from '@ant-design/icons'; +import { Button, Popover, Typography } from 'antd'; +import styles from './index.less'; + +import React, { useState, useEffect, useContext } from 'react'; +import { Tree } from 'antd'; +import type { TreeDataNode, TreeProps } from 'antd'; +import login from '@/store/login'; +import { TaskType } from '@/d.ts'; +import { ELayoutKey } from '../../const'; +import { PersonalizeLayoutContext } from '@/page/Console/PersonalizeLayoutContext'; +import { ScheduleType } from '@/d.ts/schedule'; + +const { Text } = Typography; + +// localStorage key for saving tree state +export const TREE_STATE_KEY = `personalizeLayoutTreeState-${login.organizationId}`; + +const treeData: TreeDataNode[] = [ + { + title: '快速上手', + key: ELayoutKey.QuickStart, + }, + { + title: '任务概览', + key: ELayoutKey.TaskOverview, + children: [ + { + title: '工单', + key: ELayoutKey.WorkOrder, + children: [ + { title: '导出', key: TaskType.EXPORT, isLeaf: true }, + { title: '导出结果集', key: TaskType.EXPORT_RESULT_SET }, + { title: '导入', key: TaskType.IMPORT }, + { title: '模拟数据', key: TaskType.DATAMOCK }, + { title: '数据库变更', key: TaskType.ASYNC }, + { title: '多库变更', key: TaskType.MULTIPLE_ASYNC }, + { title: '逻辑库变更', key: TaskType.LOGICAL_DATABASE_CHANGE }, + { title: '影子表同步', key: TaskType.SHADOW }, + { title: '结构比对', key: TaskType.STRUCTURE_COMPARISON }, + { title: '无锁结构变更', key: TaskType.ONLINE_SCHEMA_CHANGE }, + ], + }, + { + title: '作业', + key: ELayoutKey.Job, + children: [ + { title: '数据归档', key: ScheduleType.DATA_ARCHIVE }, + { title: '数据清理', key: ScheduleType.DATA_DELETE }, + { title: '分区计划', key: ScheduleType.PARTITION_PLAN }, + { title: 'SQL 计划', key: ScheduleType.SQL_PLAN }, + ], + }, + ], + }, + { + title: '最近访问数据库', + key: ELayoutKey.RecentDatabases, + }, + { + title: '关于我们', + key: ELayoutKey.AboutUs, + }, + { + title: '最佳实践', + key: ELayoutKey.BestPractices, + }, +]; + +export const showJobDivider = [ + ScheduleType.DATA_ARCHIVE, + ScheduleType.DATA_DELETE, + ScheduleType.PARTITION_PLAN, + ScheduleType.SQL_PLAN, +]; + +// Default state for reset functionality +export const defaultCheckedKeys = [ + ELayoutKey.QuickStart, + ELayoutKey.TaskOverview, + ELayoutKey.RecentDatabases, + ELayoutKey.AboutUs, + ELayoutKey.BestPractices, + TaskType.EXPORT, + TaskType.EXPORT_RESULT_SET, + TaskType.IMPORT, + TaskType.DATAMOCK, + TaskType.ASYNC, + TaskType.MULTIPLE_ASYNC, + TaskType.LOGICAL_DATABASE_CHANGE, + TaskType.SHADOW, + TaskType.STRUCTURE_COMPARISON, + TaskType.ONLINE_SCHEMA_CHANGE, + TaskType.ALTER_SCHEDULE, + + ScheduleType.DATA_ARCHIVE, + ScheduleType.DATA_DELETE, + ScheduleType.PARTITION_PLAN, + ScheduleType.SQL_PLAN, +]; + +const defaultExpandedKeys = [ELayoutKey.TaskOverview]; + +// Interface for saved state +interface SavedTreeState { + treeData: TreeDataNode[]; + checkedKeys: React.Key[]; + expandedKeys: React.Key[]; +} + +// Helper function to save state to localStorage +const saveTreeState = (state: SavedTreeState) => { + try { + localStorage.setItem(TREE_STATE_KEY, JSON.stringify(state)); + } catch (error) { + console.error('Failed to save tree state to localStorage:', error); + } +}; + +// Helper function to load state from localStorage +export const loadTreeState = (): SavedTreeState | null => { + try { + const savedState = localStorage.getItem(TREE_STATE_KEY); + if (savedState) { + return JSON.parse(savedState); + } + } catch (error) { + console.error('Failed to load tree state from localStorage:', error); + } + return null; +}; + +const TreeSetting = () => { + // Load initial state from localStorage or use defaults + const savedState = loadTreeState(); + const { checkedKeys, setCheckedKeys } = useContext(PersonalizeLayoutContext); + + const [gData, setGData] = useState(savedState?.treeData || treeData); + const [expandedKeys, setExpandedKeys] = useState( + savedState?.expandedKeys || defaultExpandedKeys, + ); + const [selectedKeys, setSelectedKeys] = useState([]); + const [autoExpandParent, setAutoExpandParent] = useState(true); + + // Save state to localStorage whenever it changes + useEffect(() => { + const stateToSave: SavedTreeState = { + treeData: gData, + checkedKeys, + expandedKeys, + }; + saveTreeState(stateToSave); + }, [gData, checkedKeys, expandedKeys]); + + const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => { + console.log('onExpand', expandedKeysValue); + setExpandedKeys(expandedKeysValue); + setAutoExpandParent(false); + }; + + const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => { + console.log('onCheck', checkedKeysValue); + setCheckedKeys(checkedKeysValue as React.Key[]); + }; + + const onSelect: TreeProps['onSelect'] = (selectedKeysValue, info) => { + console.log('onSelect', info); + setSelectedKeys(selectedKeysValue); + }; + + const onDragEnter: TreeProps['onDragEnter'] = (info) => { + console.log(info); + // expandedKeys, set it when controlled is needed + // setExpandedKeys(info.expandedKeys) + }; + + const onDrop: TreeProps['onDrop'] = (info) => { + const dropKey = info.node.key; + const dragKey = info.dragNode.key; + const dropPos = info.node.pos.split('-'); + const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // the drop position relative to the drop node, inside 0, top -1, bottom 1 + + const loop = ( + data: TreeDataNode[], + key: React.Key, + callback: (node: TreeDataNode, i: number, data: TreeDataNode[]) => void, + ) => { + for (let i = 0; i < data.length; i++) { + if (data[i].key === key) { + return callback(data[i], i, data); + } + if (data[i].children) { + loop(data[i].children!, key, callback); + } + } + }; + const data = [...gData]; + + // Find dragObject + let dragObj: TreeDataNode; + loop(data, dragKey, (item, index, arr) => { + arr.splice(index, 1); + dragObj = item; + }); + + if (!info.dropToGap) { + // Drop on the content + loop(data, dropKey, (item) => { + item.children = item.children || []; + // where to insert. New item was inserted to the start of the array in this example, but can be anywhere + item.children.unshift(dragObj); + }); + } else { + let ar: TreeDataNode[] = []; + let i: number; + loop(data, dropKey, (_item, index, arr) => { + ar = arr; + i = index; + }); + if (dropPosition === -1) { + // Drop on the top of the drop node + ar.splice(i!, 0, dragObj!); + } else { + // Drop on the bottom of the drop node + ar.splice(i! + 1, 0, dragObj!); + } + } + setGData(data); + }; + + const isDraggable = (node: TreeDataNode) => { + // Only allow "工单" and "作业" children to be dragged + const key = node.key as TaskType; + return [ + TaskType.EXPORT, + TaskType.EXPORT_RESULT_SET, + TaskType.IMPORT, + TaskType.DATAMOCK, + TaskType.ASYNC, + TaskType.MULTIPLE_ASYNC, + TaskType.LOGICAL_DATABASE_CHANGE, + TaskType.SHADOW, + TaskType.STRUCTURE_COMPARISON, + TaskType.ONLINE_SCHEMA_CHANGE, + ScheduleType.DATA_ARCHIVE, + ScheduleType.DATA_DELETE, + ScheduleType.PARTITION_PLAN, + ScheduleType.SQL_PLAN, + TaskType.ALTER_SCHEDULE, + ].includes(key); + }; + + const handleReset = () => { + // Clear localStorage and reset to default state + try { + localStorage.removeItem(TREE_STATE_KEY); + } catch (error) { + console.error('Failed to clear localStorage:', error); + } + + setGData(treeData); + setExpandedKeys(defaultExpandedKeys); + setCheckedKeys(defaultCheckedKeys); + setAutoExpandParent(true); + }; + + return ( +
+
+ 自定义布局 + +
+
+ isDraggable(node)} + onExpand={onExpand} + expandedKeys={expandedKeys} + autoExpandParent={autoExpandParent} + onCheck={onCheck} + checkedKeys={checkedKeys} + onSelect={onSelect} + selectedKeys={selectedKeys} + onDragEnter={onDragEnter} + onDrop={onDrop} + treeData={gData} + /> +
+
+ ); +}; + +const PersonalizeLayoutSetting = () => { + return ( + } + placement="bottomLeft" + trigger="click" + classNames={{ root: styles.customLayoutPopover }} + > + + + ); +}; +export default PersonalizeLayoutSetting; diff --git a/src/page/Console/components/QuickStart/index.less b/src/page/Console/components/QuickStart/index.less new file mode 100644 index 000000000..e68f04d84 --- /dev/null +++ b/src/page/Console/components/QuickStart/index.less @@ -0,0 +1,121 @@ +.quickStart { + .consoleCardTitle { + cursor: default; + font-size: 14px; + line-height: 22px; + font-weight: 600; + opacity: 0.85; + } + margin-bottom: 16px; + &:hover { + .consoleCardTitle { + .hide { + opacity: 1; + } + } + } + + .moreFunctionIntro { + color: #5c6b8a; + cursor: pointer; + } + .consoleCardTitle { + display: flex; + &:hover { + .hide { + opacity: 1; + } + } + .hide { + opacity: 0; + transition: top 0.3s ease; + font-weight: 400; + display: inline-flex; + margin-left: auto; + font-size: 12px; + color: var(--text-color-link); + line-height: 20px; + text-align: right; + } + } + .card { + padding: 4px; + overflow: hidden; + + :global { + .ant-card-body { + height: 364px; + padding: 12px; + background-image: linear-gradient(125deg, #f9fbfd, #e9f3fe); + } + } + .quickStartContent { + display: flex; + flex-direction: row; + .leftWrapper { + display: inline-flex; + flex-direction: column; + .descriptions { + max-width: 412px; + } + .tabs { + margin-top: 16px; + } + .steps { + height: 140px; + .stepItem { + display: flex; + cursor: pointer; + align-items: center; + height: 20px; + margin-top: 20px; + &:nth-child(1) { + margin-top: 4px; + } + + .stepIcon { + width: 16px; + height: 16px; + background-color: #d8d8d8; + border-radius: 9px; + font-size: 11px; + color: #ffffff; + line-height: 16px; + text-align: center; + font-weight: 600; + &.active { + background-color: var(--icon-color-focus); + } + } + .stepLabel { + font-size: 12px; + line-height: 20px; + opacity: 0.85; + &.active { + font-size: 12px; + color: var(--text-color-link); + line-height: 20px; + } + } + } + } + } + .rightWrapper { + display: inline-flex; + align-items: center; + margin-left: 156px; + .img { + width: 550px; + height: 278px; + } + @media screen and (max-width: 1280px) { + margin-left: 0; + .img { + width: 366px; + height: 190px; + } + } + } + } + } +} diff --git a/src/page/Console/components/QuickStart/index.tsx b/src/page/Console/components/QuickStart/index.tsx new file mode 100644 index 000000000..e808b5b77 --- /dev/null +++ b/src/page/Console/components/QuickStart/index.tsx @@ -0,0 +1,155 @@ +import { formatMessage, getImg } from '@/util/intl'; +import { Card, Divider, Radio, Tooltip, Typography } from 'antd'; +import styles from './index.less'; +import { useState } from 'react'; +import { ConsoleTextConfig, EQuickStartRole } from '../../const'; +import { gotoSQLWorkspace } from '@/util/route'; +import { URL_ACTION } from '@/util/hooks/useUrlAction'; +import { TaskPageType } from '@/d.ts'; +import { useNavigate } from '@umijs/max'; +import { IPageType } from '@/d.ts/_index'; +import LabelWithIcon from '@/component/LabelWithIcon'; +import { ExperimentOutlined } from '@ant-design/icons'; +import modal from '@/store/modal'; + +const QuickStart = () => { + const [currentQuickStartRole, setCurrentQuickStartRole] = useState(EQuickStartRole.Admin); + const [currentQuickStartStep, setCurrentQuickStartStep] = useState(-1); + const { quickStart } = ConsoleTextConfig; + + const navigate = useNavigate(); + + const quickStartMenu = { + [`${EQuickStartRole.Admin}_0`]: () => { + navigate(`/${IPageType.Datasource}?action=${URL_ACTION.newDatasource}`); + }, + [`${EQuickStartRole.Admin}_1`]: () => { + navigate(`/${IPageType.Project}?action=${URL_ACTION.newProject}`); + }, + [`${EQuickStartRole.Admin}_2`]: () => { + navigate(`/secure/riskLevel`); + }, + [`${EQuickStartRole.Admin}_3`]: () => { + navigate(`/${IPageType.Task}?action=${URL_ACTION.newTask}`); + }, + [`${EQuickStartRole.Develepor}_0`]: () => { + navigate(`/${IPageType.Project}?action=${URL_ACTION.newApply}`); + }, + [`${EQuickStartRole.Develepor}_1`]: () => { + navigate(`/${IPageType.Task}?action=${URL_ACTION.newDataMock}&task=${TaskPageType.DATAMOCK}`); + }, + [`${EQuickStartRole.Develepor}_2`]: () => { + gotoSQLWorkspace(); + }, + }; + return ( +
+ +
+ {formatMessage({ + id: 'src.page.Console.D52989BC', + defaultMessage: '快速上手', + })} +
+
+
+ { + setCurrentQuickStartRole(e.target.value); + }} + value={currentQuickStartRole} + style={{ marginBottom: 8 }} + > + {quickStart.role.map((item, index) => { + return {item}; + })} + +
+ + + {quickStart.descriptions[currentQuickStartRole]} + + +
+
{ + setCurrentQuickStartStep(-1); + }} + > + {quickStart.steps[currentQuickStartRole].map((step, index) => { + return ( +
{ + setCurrentQuickStartStep(index); + }} + onClick={() => quickStartMenu?.[`${currentQuickStartRole}_${index}`]?.()} + > + + {index + 1} + + ) + } + label={ + + {step} + + } + /> +
+ ); + })} +
+ + } + label={ + modal.changeVersionModalVisible(true)} + > + {formatMessage({ + id: 'src.page.Console.39E600CA', + defaultMessage: '更多功能介绍', + })} + + } + /> +
+
+ -1 + ? `guide/${currentQuickStartRole}-${currentQuickStartStep}.png` + : `guide/default-${currentQuickStartRole}.png`, + )} + /> +
+
+
+
+ ); +}; + +export default QuickStart; diff --git a/src/page/Console/components/ScheduleCounter/index.less b/src/page/Console/components/ScheduleCounter/index.less new file mode 100644 index 000000000..418ce0e4f --- /dev/null +++ b/src/page/Console/components/ScheduleCounter/index.less @@ -0,0 +1,26 @@ +.scheduleCounter { + display: flex; + flex-direction: column; + // justify-content: space-between; + align-items: start; + min-width: 90px; + cursor: default; + .counter { + cursor: pointer; + font-size: 24px; + line-height: 32px; + font-weight: 600; + .icon { + font-size: 12px; + color: #5c6b8a; + font-weight: 400; + line-height: 20px; + margin-left: 4px; + } + } + .title { + font-size: 14px; + line-height: 20px; + color: #5c6b8a; + } +} diff --git a/src/page/Console/components/ScheduleCounter/index.tsx b/src/page/Console/components/ScheduleCounter/index.tsx new file mode 100644 index 000000000..f6992aa85 --- /dev/null +++ b/src/page/Console/components/ScheduleCounter/index.tsx @@ -0,0 +1,21 @@ +import { RightOutlined } from '@ant-design/icons'; +import styles from './index.less'; +interface IProps { + title: string; + counter: number; + onClick?: () => void; +} + +const ScheduleCounter = ({ title, counter, onClick }: IProps) => { + return ( +
+
{title || '-'}
+
+ {counter || 0} + +
+
+ ); +}; + +export default ScheduleCounter; diff --git a/src/page/Console/components/ScheduleItem/index.less b/src/page/Console/components/ScheduleItem/index.less index 1a4b27380..60b2377c2 100644 --- a/src/page/Console/components/ScheduleItem/index.less +++ b/src/page/Console/components/ScheduleItem/index.less @@ -2,13 +2,20 @@ display: flex; align-items: center; flex-direction: column; - - margin-top: 20px; - width: 160px; - height: 248px; + width: 90px; + height: 229px; &:nth-child(1) { margin-right: 0; } + + .title { + font-size: 14px; + font-weight: 600; + color: var(--text-color-primary); + text-align: center; + cursor: default; + } + .progress { display: inline-flex; flex-direction: column; @@ -35,16 +42,18 @@ .ringChart { display: flex; justify-content: center; - margin-top: 8px; - min-height: 140px; + min-height: 130px; align-items: center; } } .counters { display: inline-flex; - flex-direction: row; + flex-direction: column; justify-content: space-around; width: 100%; + .countersDivider { + margin: 6px 0; + } } } diff --git a/src/page/Console/components/ScheduleItem/index.tsx b/src/page/Console/components/ScheduleItem/index.tsx index 9ba3ce0de..f0adac47b 100644 --- a/src/page/Console/components/ScheduleItem/index.tsx +++ b/src/page/Console/components/ScheduleItem/index.tsx @@ -6,64 +6,73 @@ import LabelWithIcon from '../../../../component/LabelWithIcon'; import styles from './index.less'; import DonutChart from '../DonutChart'; import { IPageType } from '@/d.ts/_index'; -import { ScheduleStatus } from '@/d.ts/schedule'; -const ScheduleItem = ({ title, progress, type }) => { - const { statusType } = ConsoleTextConfig.schdules; - const { successEnabledCount } = progress || {}; - const { failedExecutionCount } = progress?.taskStat || {}; - const navigate = useNavigate(); +import { TaskExecStrategy, TaskStatus, TaskType } from '@/d.ts'; +import { Divider } from 'antd'; +import { IStat } from '@/d.ts'; +import { ScheduleStatus, ScheduleType } from '@/d.ts/schedule'; - const total = progress?.taskStat - ? statusType.reduce((sum, key) => sum + (parseInt(progress?.taskStat?.[key]) || 0), 0) - : undefined; +const ScheduleItem = ({ + title, + progress, + type, +}: { + title: string; + progress: IStat; + type: ScheduleType; +}) => { + const { count } = progress || {}; + const { PENDING, EXECUTING, EXECUTION_FAILURE, EXECUTION_SUCCESS, OTHER, ENABLED } = count || {}; + const totalCount = + (PENDING || 0) + + (EXECUTING || 0) + + (EXECUTION_FAILURE || 0) + + (EXECUTION_SUCCESS || 0) + + (OTHER || 0); + const navigate = useNavigate(); return (
+
{title}
- {title}} - label={ - - navigate( - `/${IPageType.Schedule}?scheduleType=${type}&scheduleStatus=${ScheduleStatus.ENABLED}`, - ) - } - > - {formatMessage({ - id: 'src.page.Console.components.ScheduleItem.4E8811DF', - defaultMessage: '已启用', - })} - {successEnabledCount || 0} - {formatMessage({ - id: 'src.page.Console.components.ScheduleItem.728A17A0', - defaultMessage: '个', - })} - - } - gap={4} - align={['vertical', 'center']} - /> -
- +
{ + // 跳转到调度管理页面,设置特定类型和已启用状态过滤 + navigate(`/schedule?scheduleStatus=${ScheduleStatus.ENABLED}&scheduleType=${type}`); + }} + title={formatMessage({ + id: 'src.page.Console.components.ScheduleItem.4E8811DF', + defaultMessage: '已启用', + })} + counter={ENABLED || 0} + /> + + { + navigate(`/schedule?scheduleType=${type}`); + }} title={formatMessage({ id: 'src.page.Console.components.ScheduleItem.1ADBD842', defaultMessage: '共执行', })} - counter={total} + counter={totalCount} /> { + // 跳转到调度管理页面,设置特定类型和已启用状态过滤 + navigate( + `/schedule?scheduleStatus=${ScheduleStatus.EXECUTION_FAILED}&scheduleType=${type}`, + ); + }} title={formatMessage({ id: 'src.page.Console.components.ScheduleItem.6F6BDC9E', defaultMessage: '执行失败', })} - counter={parseInt(failedExecutionCount)} + counter={EXECUTION_FAILURE || 0} status="failed" />
diff --git a/src/page/Console/const.ts b/src/page/Console/const.tsx similarity index 79% rename from src/page/Console/const.ts rename to src/page/Console/const.tsx index f847f6c52..b7c3b1836 100644 --- a/src/page/Console/const.ts +++ b/src/page/Console/const.tsx @@ -1,6 +1,6 @@ import { formatMessage } from '@/util/intl'; +import { TaskType } from '@/d.ts'; import { ScheduleType } from '@/d.ts/schedule'; -import { title } from 'process'; export enum EQuickStartRole { Admin, @@ -15,25 +15,79 @@ export enum EDatabaseTableColumnKey { Operation = 'operation', } +export enum ELayoutKey { + QuickStart = 'quick-start', + TaskOverview = 'task-overview', + WorkOrder = 'work-order', + Job = 'job', + RecentDatabases = 'recent-databases', + AboutUs = 'about-us', + BestPractices = 'best-practices', +} + +export const TaskTypes = [ + TaskType.EXPORT, + TaskType.EXPORT_RESULT_SET, + TaskType.IMPORT, + TaskType.DATAMOCK, + TaskType.ASYNC, + TaskType.MULTIPLE_ASYNC, + TaskType.LOGICAL_DATABASE_CHANGE, + TaskType.SHADOW, + TaskType.STRUCTURE_COMPARISON, + TaskType.ONLINE_SCHEMA_CHANGE, +]; + +export const TaskTitle = { + [TaskType.EXPORT]: '导出', + [TaskType.EXPORT_RESULT_SET]: '导出结果集', + [TaskType.IMPORT]: '导入', + [TaskType.DATAMOCK]: '模拟数据', + [TaskType.ASYNC]: '数据库变更', + [TaskType.MULTIPLE_ASYNC]: '多库变更', + [TaskType.LOGICAL_DATABASE_CHANGE]: '逻辑库变更', + [TaskType.SHADOW]: '影子表同步', + [TaskType.STRUCTURE_COMPARISON]: '结构比对', + [TaskType.ONLINE_SCHEMA_CHANGE]: '无锁结构变更', +}; + +export const ScheduleTitle = { + [ScheduleType.DATA_ARCHIVE]: formatMessage({ + id: 'src.page.Console.B92D6192', + defaultMessage: '数据归档', + }), + [ScheduleType.DATA_DELETE]: formatMessage({ + id: 'src.page.Console.E2F84D37', + defaultMessage: '数据清理', + }), + [ScheduleType.PARTITION_PLAN]: formatMessage({ + id: 'src.page.Console.A9C48F30', + defaultMessage: '分区计划', + }), + [ScheduleType.SQL_PLAN]: formatMessage({ + id: 'src.page.Console.E561DFFF', + defaultMessage: 'SQL 计划', + }), +}; + +export const ScheduleTypes = [ + ScheduleType.DATA_ARCHIVE, + ScheduleType.DATA_DELETE, + ScheduleType.PARTITION_PLAN, + ScheduleType.SQL_PLAN, +]; + export const ConsoleTextConfig = { schdules: { keys: ['dataArchive', 'dataClear', 'partition', 'sqlPlan'], status: [ + '待调度', + formatMessage({ id: 'src.page.Console.095A8212', defaultMessage: '执行中' }), formatMessage({ id: 'src.page.Console.4D58E4BD', defaultMessage: '执行成功' }), formatMessage({ id: 'src.page.Console.0DE02703', defaultMessage: '执行失败' }), - formatMessage({ id: 'src.page.Console.095A8212', defaultMessage: '执行中' }), - formatMessage({ id: 'src.page.Console.A46DFDCA', defaultMessage: '待执行' }), formatMessage({ id: 'src.page.Console.32807E76', defaultMessage: '其他' }), ], - - statusType: [ - 'successExecutionCount', - 'failedExecutionCount', - 'executingCount', - 'waitingExecutionCount', - 'otherCount', - ], - + statusType: ['PENDING', 'EXECUTING', 'EXECUTION_SUCCESS', 'EXECUTION_FAILURE', 'OTHER'], statusColor: ['#73d13d', '#ff6667', '#40a9ff', '#91d5ff', '#e0e0e0'], scheduleTitle: [ formatMessage({ id: 'src.page.Console.B92D6192', defaultMessage: '数据归档' }), diff --git a/src/page/Console/index.less b/src/page/Console/index.less index 4f80a46b1..122393d8e 100644 --- a/src/page/Console/index.less +++ b/src/page/Console/index.less @@ -37,6 +37,10 @@ .header { height: 74px; gap: 8px; + display: flex; + justify-content: space-between; + align-items: flex-end; + padding-bottom: 16px; .title { font-size: 20px; line-height: 28px; @@ -57,26 +61,27 @@ background-color: #fff; } .top { - height: 372px; - margin-bottom: 16px; - - .schedules { - .consoleCardTitle { - .title { - display: inline-block; - margin-right: 12px; - } - .consoleTips { - display: inline-block; - font-size: 12px; - color: var(--text-color-hint); - line-height: 22px; - font-weight: 400; + .schedules, + .schedulesVertical { + .header { + display: flex; + justify-content: space-between; + align-items: center; + height: 28px; + .filter { + height: 28px; + min-width: 103px; + margin-left: 8px; + :global { + .anticon { + color: var(--icon-color-normal); + } + } } } - .card { - height: 372px; + height: 486px; + margin-bottom: 16px; .icon { display: inline-block; width: 8px; @@ -85,126 +90,91 @@ .legend { display: flex; gap: 16px; + padding-bottom: 20px; + @media screen and (max-width: 1280px) { + padding-bottom: 0; + margin-top: 20px; + } + height: 20px; - margin-top: 8px; .label { color: var(--text-color-secondary); cursor: default; } } } - .scheduleItems { - display: flex; - flex-direction: row; - justify-content: space-between; + @media screen and (max-width: 1280px) { + .divider { + display: none; + } + .card { + height: 764px; + } } - } + .jobsCounter { + background: #f8fafe; + border-radius: 2px; + width: 100%; + height: 80px; + margin: 13px 0 40px 0; - .quickStart { - &:hover { - .consoleCardTitle { - .hide { - opacity: 1; - } + .counterGrid { + display: flex; + justify-content: space-around; + align-items: center; + height: 100%; + padding: 0 20px; } } - .moreFunctionIntro { - color: var(--text-color-secondary); - cursor: pointer; - } - .consoleCardTitle { + .chartsContainer, + .chartsContainerVertical { display: flex; - &:hover { - .hide { - opacity: 1; - } + margin-top: 20px; + gap: 24px; + width: 100%; + min-height: 252px; + + .barChartSection { + flex: 1; + min-width: 0; } - .hide { - opacity: 0; - transition: top 0.3s ease; - font-weight: 400; - display: inline-flex; - margin-left: auto; - font-size: 12px; - color: var(--text-color-link); - line-height: 20px; - text-align: right; + .divider { + height: 232px; + margin: 0; } - } - .card { - padding: 4px; - overflow: hidden; - :global { - .ant-card-body { - height: 364px; - padding: 12px; - background-image: linear-gradient(125deg, #f9fbfd, #e9f3fe); + .pieChartSection { + min-width: 0; + + .scheduleItems { + display: flex; + gap: 71px; } } - .quickStartContent { - display: flex; - flex-direction: row; - .leftWrapper { - display: inline-flex; - flex-direction: column; - .descriptions { - max-width: 412px; - } - .tabs { - margin-top: 16px; - } - .steps { - height: 140px; - .stepItem { - display: flex; - cursor: pointer; - align-items: center; - height: 20px; - margin-top: 20px; - &:nth-child(1) { - margin-top: 4px; - } + } - .stepIcon { - width: 16px; - height: 16px; - background-color: #d8d8d8; - border-radius: 9px; - font-size: 11px; - color: #ffffff; - line-height: 16px; - text-align: center; - font-weight: 600; - &.active { - background-color: var(--icon-color-focus); - } - } - .stepLabel { - font-size: 12px; - line-height: 20px; - opacity: 0.85; - &.active { - font-size: 12px; - color: var(--text-color-link); - line-height: 20px; - } - } - } - } - } - .rightWrapper { - display: inline; - margin-left: 156px; - .gif { - width: 400px; - height: 300px; + // 响应式布局 + @media (max-width: 1280px) { + .chartsContainer { + flex-direction: column; + gap: 16px; + + .pieChartSection { + .scheduleItems { + justify-content: space-between; } } } } } + .schedulesVertical { + @media (max-width: 1280px) { + .card { + height: 486px; + } + } + } } .bottom { @@ -224,10 +194,12 @@ } } - .docWrapper { + .docWrapper, + .docWrapperVertical { padding-left: 8px; - .aboutUs { + .aboutUs, + .aboutUsLargeVersion { height: 174px; :global { .ant-card-body { @@ -235,8 +207,19 @@ } } } + .aboutUsLargeVersion { + height: 521px; + .aboutUsContent { + flex-direction: column; + gap: 78px; + .docsWrapper { + width: 100%; + } + } + } - .practice { + .practice, + .practiceLargeVerion { height: 332px; :global { .ant-card-body { @@ -260,6 +243,9 @@ } } } + .practiceLargeVerion { + height: 521px; + } .aboutUsHelpDocItem { height: 20px; @@ -303,6 +289,39 @@ } } } + .docWrapperVertical { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + gap: 16px; + .aboutUs { + flex: 1; + height: 206px; + .aboutUsContent { + justify-content: flex-start; + align-items: center; + .labelWithIcon { + width: 50%; + } + .docsWrapper { + width: 50%; + } + } + } + .practice { + flex: 1; + margin-top: 0; + height: 206px; + overflow-y: hidden; + &:nth-child(2) { + margin-top: 0px; + } + } + .article { + margin-top: 8px; + } + } } } } diff --git a/src/page/Console/index.tsx b/src/page/Console/index.tsx index 40d66ef9e..ce44ee559 100644 --- a/src/page/Console/index.tsx +++ b/src/page/Console/index.tsx @@ -1,34 +1,50 @@ import { formatMessage, getLocalDocs, getOBDocsUrl } from '@/util/intl'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useContext, useEffect, useMemo, useState } from 'react'; import { useMount, useRequest } from 'ahooks'; -import modal from '@/store/modal'; -import { Card, Col, Divider, Popconfirm, Radio, Row, Spin, Tooltip, Typography } from 'antd'; +import dayjs, { Dayjs } from 'dayjs'; +import { Card, Col, Divider, Row, Select, Spin, Typography, DatePicker } from 'antd'; import odc from '@/plugins/odc'; import { ReactComponent as DownloadSvg } from '@/svgr/download-fill.svg'; import { ReactComponent as GithubSvg } from '@/svgr/github.svg'; import { ReactComponent as SendSvg } from '@/svgr/send-fill.svg'; -import { getImg } from '@/util/intl'; - -import Icon, { ExperimentOutlined } from '@ant-design/icons'; +import Icon, { ClockCircleOutlined } from '@ant-design/icons'; import LabelWithIcon from '../../component/LabelWithIcon'; import ScheduleItem from './components/ScheduleItem'; -import { areaLayout, ConsoleTextConfig, EQuickStartRole, gridConfig } from './const'; +import BarChart from './components/BarChart'; +import { ConsoleTextConfig, ScheduleTitle, ScheduleTypes, ELayoutKey, TaskTypes } from './const'; import styles from './index.less'; import RecentlyDatabase from './components/RecentlyDatabase'; import { useNavigate } from '@umijs/max'; +import { getFlowScheduleTodo, getTaskStat } from '@/common/network/task'; +import { + IGetFlowScheduleTodoParams, + ITaskStatParam, + TaskPageType, + TaskType, + TaskStatus, +} from '@/d.ts'; import { getScheduleStat } from '@/common/network/schedule'; -import { ICycleTaskStatParam, TaskPageType } from '@/d.ts'; -import login from '@/store/login'; -import setting from '@/store/setting'; import { IPageType } from '@/d.ts/_index'; -import { gotoSQLWorkspace } from '@/util/route'; -import { URL_ACTION } from '@/util/hooks/useUrlAction'; +import { TaskTab } from '@/component/Task/interface'; +import { ScheduleTab } from '@/component/Schedule/interface'; +import login from '@/store/login'; import QrCodeWithIcon from './components/QRCodeWithIcon'; -import { sumTaskStats } from '@/util/utils'; +import ScheduleCounter from './components/ScheduleCounter'; +import PersonalizeLayoutSetting, { showJobDivider } from './components/PersonalizeLayoutSetting'; +import { + PersonalizeLayoutContext, + PersonalizeLayoutProvider, +} from '@/page/Console/PersonalizeLayoutContext'; +import { TimeOptions } from '@/component/TimeSelect'; -const paddingCal = (currentLayout) => { - return currentLayout === gridConfig.all ? 0 : 8; -}; +import { listProjects } from '@/common/network/project'; +import QuickStart from './components/QuickStart'; +import { ScheduleStatus, ScheduleType } from '@/d.ts/schedule'; + +const { RangePicker } = DatePicker; +const cacheProjectIdKey = `odc-front-page-project-${login.organizationId}`; +const cacheTimeKey = `odc-front-page-time-${login.organizationId}`; +const cacheDateKey = `odc-front-page-date-${login.organizationId}`; const aboutUsIcons = [ , @@ -36,407 +52,392 @@ const aboutUsIcons = [ , ]; -const Console = () => { - const { quickStart, aboutUs, bestPractice, schdules } = ConsoleTextConfig; - const [currentQuickStartRole, setCurrentQuickStartRole] = useState(EQuickStartRole.Admin); - const [currentQuickStartStep, setCurrentQuickStartStep] = useState(-1); - const [topAreaLayout, setTopAreaLayout] = useState(areaLayout.both); +const ConsoleMain = () => { + const { aboutUs, bestPractice } = ConsoleTextConfig; + const { status, statusColor } = ConsoleTextConfig.schdules; + const cacheTimeValue = localStorage.getItem(cacheTimeKey); + const cacheDateValue = localStorage.getItem(cacheDateKey); + + const [timeValue, setTimeValue] = useState(() => { + if (!cacheTimeValue) return 7; + try { + return JSON.parse(cacheTimeValue); + } catch (error) { + console.error('Failed to parse cached time value:', error); + return 7; + } + }); + const [dateValue, setDateValue] = useState<[Dayjs, Dayjs] | null>(() => { + if (!cacheDateValue) return null; + try { + const timestamps = JSON.parse(cacheDateValue); + if (Array.isArray(timestamps) && timestamps.length === 2) { + return [dayjs(timestamps[0]), dayjs(timestamps[1])]; + } + } catch (error) { + console.error('Failed to parse cached date value:', error); + } + return null; + }); + + const { checkedKeys } = useContext(PersonalizeLayoutContext); + const cacheProjectId = localStorage.getItem(cacheProjectIdKey); + + const [selectedProjectId, setSelectedProjectId] = useState( + cacheProjectId && Number(cacheProjectId) > -1 ? Number(cacheProjectId) : undefined, + ); + const navigate = useNavigate(); - useEffect(() => { - setCurrentQuickStartStep(-1); - }, [currentQuickStartRole]); + const { run: runListProjects, data: projects } = useRequest(listProjects, { + defaultParams: [null, null, null], + }); + + const { data: todosData, run: runGetFlowScheduleTodo } = useRequest( + (params: IGetFlowScheduleTodoParams) => getFlowScheduleTodo(params), + { + manual: true, + }, + ); const { - data, + data: schedulesData, loading: scheduleLoading, run: runGetScheduleStat, - refresh, - } = useRequest((params: ICycleTaskStatParam) => getScheduleStat(params), { + } = useRequest((params: ITaskStatParam) => getScheduleStat(params), { manual: true, }); - const schedulesData = useMemo(() => { - const _schedules = { totalCount: 0 }; - data?.forEach((item) => { - const taskStat = sumTaskStats(item.taskStats); - const schedule = { ...item, taskStat }; - _schedules[item?.type] = schedule; - _schedules.totalCount += Number(item?.totalCount || 0); - }); - return _schedules; - }, [data]); - useEffect(() => { - const enable = setting.configurations?.['odc.user.guidePromptEnabled']; - const guidePromptEnabled = enable && enable !== 'true'; - const hasData = schedulesData?.totalCount > 0; - if (guidePromptEnabled) { - setTopAreaLayout(hasData ? areaLayout.schedules : areaLayout.hideTop); - } else { - setTopAreaLayout(hasData ? areaLayout.both : areaLayout.quickStart); - } - }, [schedulesData]); + const { + data: taskResData, + loading: taskLoading, + run: runGetTaskStat, + } = useRequest((params: ITaskStatParam) => getTaskStat(params), { + manual: true, + }); useMount(() => { + runListProjects(null, null, null); + }); + + useEffect(() => { + runGetFlowScheduleTodo({ + currentOrganizatonId: login.organizationId, + projectId: selectedProjectId, + }); runGetScheduleStat({ currentOrganizationId: login.organizationId, types: ['DATA_ARCHIVE', 'SQL_PLAN', 'DATA_DELETE', 'PARTITION_PLAN'], - startTime: Date.now() - 1000 * 60 * 60 * 24 * 7, - endTime: Date.now(), + ...timeSetting, + projectId: selectedProjectId, }); - }); + runGetTaskStat({ + currentOrganizationId: login.organizationId, + types: TaskTypes, + ...timeSetting, + projectId: selectedProjectId, + }); + }, [selectedProjectId, timeValue, dateValue]); - const quickStartMenu = { - [`${EQuickStartRole.Admin}_0`]: () => { - navigate(`/${IPageType.Datasource}?action=${URL_ACTION.newDatasource}`); - }, - [`${EQuickStartRole.Admin}_1`]: () => { - navigate(`/${IPageType.Project}?action=${URL_ACTION.newProject}`); - }, - [`${EQuickStartRole.Admin}_2`]: () => { - navigate(`/secure/riskLevel`); - }, - [`${EQuickStartRole.Admin}_3`]: () => { - navigate(`/${IPageType.Task}?action=${URL_ACTION.newTask}`); - }, - [`${EQuickStartRole.Develepor}_0`]: () => { - navigate(`/${IPageType.Project}?action=${URL_ACTION.newApply}`); - }, - [`${EQuickStartRole.Develepor}_1`]: () => { - navigate(`/${IPageType.Task}?action=${URL_ACTION.newDataMock}&task=${TaskPageType.DATAMOCK}`); - }, - [`${EQuickStartRole.Develepor}_2`]: () => { - gotoSQLWorkspace(); - }, - }; + const projectOptions = useMemo(() => { + return projects?.contents.map((item) => ({ label: item.name, value: item.id })) || []; + }, [projects]); + + const boardVisible = useMemo(() => { + const isChecked = (key: ELayoutKey) => checkedKeys.includes(key); + + return { + [ELayoutKey.AboutUs]: isChecked(ELayoutKey.AboutUs), + [ELayoutKey.BestPractices]: isChecked(ELayoutKey.BestPractices), + [ELayoutKey.QuickStart]: isChecked(ELayoutKey.QuickStart), + [ELayoutKey.RecentDatabases]: isChecked(ELayoutKey.RecentDatabases), + }; + }, [checkedKeys]); + + const timeSetting = useMemo(() => { + if (String(timeValue) === 'ALL') { + return {}; + } + if (String(timeValue) === 'custom' && dateValue?.[0] && dateValue?.[1]) { + return { + startTime: dateValue[0].valueOf(), + endTime: dateValue[1].valueOf(), + }; + } + return { + startTime: Date.now() - 1000 * 60 * 60 * 24 * Number(timeValue), + endTime: Date.now(), + }; + }, [timeValue, dateValue]); + + const articles = useMemo(() => { + return checkedKeys.includes(ELayoutKey.RecentDatabases) + ? bestPractice.articles + : bestPractice.articles?.slice(0, 5); + }, [checkedKeys]); + + const schedules = useMemo(() => { + return ScheduleTypes.filter((item) => checkedKeys.includes(item)).map((type) => ({ + title: ScheduleTitle[type], + type, + })); + }, [checkedKeys]); - const renderScheduleCard = useCallback(() => { - const { status, statusColor } = ConsoleTextConfig.schdules; - return ( - -
- - {formatMessage({ id: 'src.page.Console.21065126', defaultMessage: '定时任务概览' })} - - - {formatMessage({ id: 'src.page.Console.675B230B', defaultMessage: '(近 7 天)' })} - -
-
- {status.map((item, index) => { - return ( - - } - label={{item}} - gap={8} - /> - ); - })} -
-
- {schdules?.scheduleTitle.map((title, index) => { - return ( - - ); - })} -
-
- ); - }, [schedulesData]); return ( <>
-
- {formatMessage({ - id: 'src.page.Console.BEABD6A7', - defaultMessage: '欢迎使用 OceanBase 开发者中心', - })} -
-
- {formatMessage({ - id: 'src.page.Console.94172A72', - defaultMessage: '开源的数据库开发和数据库管控协同工具', - })} +
+
+ {formatMessage({ + id: 'src.page.Console.BEABD6A7', + defaultMessage: '欢迎使用 OceanBase 开发者中心', + })} +
+
+ {formatMessage({ + id: 'src.page.Console.94172A72', + defaultMessage: '开源的数据库开发和数据库管控协同工具', + })} +
+
- - {topAreaLayout.guide || topAreaLayout.schedule ? ( - -
- {renderScheduleCard()} - - - -
- {formatMessage({ - id: 'src.page.Console.D52989BC', - defaultMessage: '快速上手', - })} - - - {' '} -
- - {formatMessage({ - id: 'src.page.Console.3791DCBC', - defaultMessage: '确认要隐藏快速上手内容吗?', - })} - -
-
- - {formatMessage({ - id: 'src.page.Console.8C05BBCA', - defaultMessage: '你也可以在帮助中重新查看。', - })} - -
- - } - onConfirm={() => { - setting.updateOneUserConfig({ - key: 'odc.user.guidePromptEnabled', - value: false, - }); - setTopAreaLayout( - data?.length > 0 ? areaLayout.schedules : areaLayout.hideTop, + +
+ {boardVisible[ELayoutKey.QuickStart] && } +
2 ? styles.schedules : styles.schedulesVertical}> + +
+
任务概览
+
+ { + setSelectedProjectId(value === -1 ? undefined : value); + localStorage.setItem(cacheProjectIdKey, value + ''); + }} + /> +
+
+
+
+ { + navigate(`/${IPageType.Task}?tab=${TaskTab.approveByCurrentUser}`); + }} + /> + { + navigate(`/${IPageType.Task}?tab=${TaskTab.executionByCurrentUser}`); + }} + /> + { + navigate( + `/${IPageType.Schedule}?tab=${ScheduleTab.approveByCurrentUser}`, ); }} - > - - {formatMessage({ - id: 'src.page.Console.210E7550', - defaultMessage: '不再提示', - })} - - + />
-
-
- { - setCurrentQuickStartRole(e.target.value); - }} - value={currentQuickStartRole} - style={{ marginBottom: 8 }} - > - {quickStart.role.map((item, index) => { - return {item}; - })} - -
- - - {quickStart.descriptions[currentQuickStartRole]} - - -
-
{ - setCurrentQuickStartStep(-1); - }} - > - {quickStart.steps[currentQuickStartRole].map((step, index) => { - return ( -
{ - setCurrentQuickStartStep(index); - }} - onClick={() => - quickStartMenu?.[`${currentQuickStartRole}_${index}`]?.() - } - > - - {index + 1} - - ) - } - label={ - - {step} - - } - /> -
- ); - })} -
- +
+
2 ? styles.chartsContainer : styles.chartsContainerVertical + } + > +
+ +
+ {checkedKeys.some((item) => showJobDivider.includes(item as ScheduleType)) && ( + + )} +
+
+ {schedules.map((item, index) => { + return ( + + ); + })} +
+
+
+
+ {status.map((item, index) => { + return ( - } - label={ modal.changeVersionModalVisible(true)} - > - {formatMessage({ - id: 'src.page.Console.39E600CA', - defaultMessage: '更多功能介绍', - })} - + className={styles.icon} + style={{ + backgroundColor: statusColor[index], + }} + /> } + label={{item}} + gap={8} /> -
- {topAreaLayout.guide === gridConfig.all && ( -
- -1 - ? `guide/${currentQuickStartRole}-${currentQuickStartStep}.png` - : `guide/default-${currentQuickStartRole}.png`, - )} - /> -
- )} -
- - - - ) : null} + ); + })} +
+
+
+
-
- -
- {formatMessage({ - id: 'src.page.Console.7492F9E4', - defaultMessage: '最近访问数据库', - })} -
- -
- - - -
- {formatMessage({ id: 'src.page.Console.3A3E34F5', defaultMessage: '关于我们' })} -
-
-
- {aboutUs.helps.map((help, index) => { - return ( -
- { - window.open(getOBDocsUrl(aboutUs.urlKeys[index])); - }} - > - {help} -
- } - /> -
- ); + {boardVisible[ELayoutKey.RecentDatabases] && ( +
+ +
+ {formatMessage({ + id: 'src.page.Console.7492F9E4', + defaultMessage: '最近访问数据库', })}
- } - gap={4} - label={ - - {formatMessage({ - id: 'src.page.Console.30113922', - defaultMessage: '钉钉群:67365031753', - })} - - } - /> - -
- -
- {formatMessage({ id: 'src.page.Console.41EC22B4', defaultMessage: '最佳实践' })} + + + + )} +
+ {boardVisible[ELayoutKey.AboutUs] && ( + +
+ {formatMessage({ id: 'src.page.Console.3A3E34F5', defaultMessage: '关于我们' })} +
+
+
+ {aboutUs.helps.map((help, index) => { + return ( +
+ { + window.open(getOBDocsUrl(aboutUs.urlKeys[index])); + }} + > + {help} +
+ } + /> +
+ ); + })} +
+ } + gap={4} + label={ + + {formatMessage({ + id: 'src.page.Console.30113922', + defaultMessage: '钉钉群:67365031753', + })} + + } + /> + +
+ )} + {boardVisible[ELayoutKey.BestPractices] && ( + +
+ {formatMessage({ id: 'src.page.Console.41EC22B4', defaultMessage: '最佳实践' })} - { - window.open( - odc.appConfig.docs.url - ? getOBDocsUrl('100.sql-development-common-techniques.html') - : getLocalDocs('100.sql-development-common-techniques.html'), - ); - }} - > - {formatMessage({ id: 'src.page.Console.E60EAE10', defaultMessage: '更多 >' })} - -
- {bestPractice.articles.map((article) => { - return ( -
{ window.open( odc.appConfig.docs.url - ? getOBDocsUrl(article.fragmentIdentifier) - : getLocalDocs(article.fragmentIdentifier), + ? getOBDocsUrl('100.sql-development-common-techniques.html') + : getLocalDocs('100.sql-development-common-techniques.html'), ); }} > - {article.title} -
- ); - })} -
+ {formatMessage({ id: 'src.page.Console.E60EAE10', defaultMessage: '更多 >' })} + + + {articles?.map((article) => { + return ( +
{ + window.open( + odc.appConfig.docs.url + ? getOBDocsUrl(article.fragmentIdentifier) + : getLocalDocs(article.fragmentIdentifier), + ); + }} + > + {article.title} +
+ ); + })} + + )} @@ -445,4 +446,12 @@ const Console = () => { ); }; +const Console = () => { + return ( + + + + ); +}; + export default Console; diff --git a/src/util/utils.ts b/src/util/utils.ts index 46e952916..3754fad08 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -689,20 +689,6 @@ export const CRLFToSeparatorString = (separator: string) => { return separator?.replace(/\r/g, '\\r').replace(/\n/g, '\\n'); }; -export const sumTaskStats = (taskStats) => { - return taskStats.reduce((accumulator, current) => { - Object.keys(current).forEach((key) => { - if (typeof current[key] === 'number') { - // 确保只处理数值类型的键 - if (!accumulator[key]) { - accumulator[key] = 0; - } - accumulator[key] += current[key]; - } - }); - return accumulator; - }, {}); -}; export function groupBySessionId(filteredRows) { const sessionMap = new Map(); From 0db515b5f4318fd357a8ca67efee2d8caa61d19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Thu, 28 Aug 2025 15:04:21 +0800 Subject: [PATCH 029/239] PullRequest: 962 feat: merge 440 to 441 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'merge/440To441 of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/962 Reviewed-by: 畅晚 * PullRequest: 902 补充索引名称限制输入空格 * PullRequest: 904 SQL结果集Tab管理与锁定功能优化 * PullRequest: 906 fix: 资源树数据源名称太长搜索按钮被挡住的问题 * PullRequest: 907 fix: 440bugfix * PullRequest: 897 feat: 数据归档/数据清理增加配置项 * PullRequest: 908 修正 addOperations 逻辑判定 * PullRequest: 909 按实际顺序展示 columnText * PullRequest: 910 按 sqlId 判定,避免结果重复渲染 * PullRequest: 912 feat: 适配后端数据库列表查询优化 * feat: add windows sign tool * fix: add publishername * refactor(client): rewrite path getter * PullRequest: 915 feat: 修复个人空间数据库列表权限bug * PullRequest: 918 fix: 440bugfix * PullRequest: 922 fix: 风险识别规则bug修复 * PullRequest: 923 440bugfix * PullRequest: 924 fix: 库选择组件,工单详情样式优化 * PullRequest: 926 fix: 补充数值校验 * PullRequest: 928 fix:修式优化 * PullRequest: 932 区分workSpace和project的CreateModal注册,并更新antd属性 * PullRequest: 933 fix: project 中CreateModals 添加 projectId * PullRequest: 936 440bugfix * feat(client): donwload h2 deps from oss * PullRequest: 941 feat: 用户设置添加前端缓存 * feat: hide lock file * feat: add fake lock * build(pkg): add local registry * fix(client): fix windows cmd bug * fix: execute sql from file to avoid exceeding cmd line character limits * fix: add log * fix: Enable stdout for script execution * PullRequest: 955 新增关闭结果集功能并支持追加模式下的操作 * PullRequest: 956 fix: 有未授权数据库资源时,不关闭当前创建视图页面 * PullRequest: 957 fix: char 类型增加数值 * PullRequest: 959 补充对 */n 输入的支持 * PullRequest: 960 fix(odcSetting): 桌面版数据库变更默认执行方式配置隐藏定时执行 * PullRequest: 961 merge cloud/202504 到 dev-4.4.0 * fix: 补充解决冲突的提示缺失 && 执行 odc-install * 补充jar文件 --- .gitignore | 3 +- README.md | 2 +- pnpm-lock.yaml => hidden.yaml | 944 ++++++++++++++++-- package.json | 5 +- scripts/clientDependencies/pullJar.js | 22 +- scripts/rename.js | 25 + src/common/network/task.ts | 5 +- src/component/Crontab/utils.ts | 3 +- src/component/ODCSetting/config/common.tsx | 19 +- src/component/SQLConfig/index.tsx | 4 +- .../SchduleExecutionMethodForm/index.tsx | 10 +- .../SubmitTripartiteTaskButton.tsx | 10 +- .../AsyncTaskOperationButton/helper.tsx | 41 +- .../hooks/useTaskTable.ts | 6 +- .../AsyncTaskOperationButton/index.tsx | 12 +- .../component/DataTransferModal/index.tsx | 10 +- .../Task/component/DatabaseSelect/index.tsx | 20 +- .../Task/component/ExecuteFailTip/index.tsx | 17 + .../ImportModal/DatabaseChangeItem.tsx | 57 ++ .../ImportModal/DatabaseInfoPopover.tsx | 137 +++ .../ImportModal/ImportPreviewTable.tsx | 562 +++++------ .../Task/component/ImportModal/index.less | 25 + .../Task/component/ImportModal/index.tsx | 286 ++++-- .../Task/component/ImportModal/useColumn.tsx | 296 ++++++ .../Task/component/ImportModal/useImport.tsx | 86 +- .../PartitionPolicyFormTable/configModal.tsx | 28 +- .../Task/component/TaskTable/index.tsx | 17 +- .../DetailContent/index.tsx | 9 +- .../ApplyPermission/DetailContent/index.tsx | 2 +- .../DetailContent/index.tsx | 8 +- .../DataMockerTask/CreateModal/form.tsx | 2 +- .../DataMockerTask/CreateModal/index.tsx | 2 +- .../DetailContent/index.tsx | 2 +- .../MutipleAsyncTask/DetailContent/index.tsx | 4 +- .../CreateModal/index.tsx | 4 +- src/d.ts/datasource.ts | 7 + src/d.ts/importTask.ts | 42 +- src/d.ts/migrateTask.tsx | 1 + src/locales/must/strings/en-US.json | 118 +-- src/locales/must/strings/zh-CN.json | 106 +- src/locales/must/strings/zh-TW.json | 106 +- src/main/server/main.ts | 108 +- src/main/utils/h2.ts | 42 +- src/page/Auth/User/index.tsx | 8 + .../components/RecentlyDatabase/index.less | 5 +- .../components/RecentlyDatabase/index.tsx | 197 ++-- src/page/Console/const.tsx | 5 + src/page/Console/index.less | 1 - src/page/Console/index.tsx | 2 + .../ResourceTree/DatabaseTree/index.tsx | 7 +- .../ResourceTree/DatabaseTree/useGroupData.ts | 21 +- .../Workspace/SideBar/ResourceTree/const.ts | 6 +- .../Workspace/SideBar/ResourceTree/index.tsx | 8 +- .../components/CreateViewPage/index.tsx | 7 +- .../components/DDLResultSet/index.tsx | 42 +- .../Workspace/components/SQLPage/index.tsx | 45 + .../components/SQLResultSet/index.tsx | 82 +- .../SessionSelect/SelectItem.tsx | 31 +- .../SessionSelect/SessionDropdown/helper.tsx | 11 +- .../SessionSelect/SessionDropdown/index.tsx | 135 ++- .../components/TablePage/TableData/index.tsx | 7 +- src/page/Workspace/index.tsx | 1 - src/plugins/defaultConfig.ts | 1 + src/store/setting.ts | 7 + src/store/sql/index.tsx | 44 +- src/svgr/source_database.svg | 7 + src/svgr/target_database.svg | 7 + src/util/makeDataShareable.ts | 192 ++++ src/util/sql.ts | 3 + yarn.lock | 49 + 70 files changed, 3119 insertions(+), 1027 deletions(-) rename pnpm-lock.yaml => hidden.yaml (95%) create mode 100644 scripts/rename.js create mode 100644 src/component/Task/component/ExecuteFailTip/index.tsx create mode 100644 src/component/Task/component/ImportModal/DatabaseChangeItem.tsx create mode 100644 src/component/Task/component/ImportModal/DatabaseInfoPopover.tsx create mode 100644 src/component/Task/component/ImportModal/useColumn.tsx create mode 100644 src/svgr/source_database.svg create mode 100644 src/svgr/target_database.svg create mode 100644 src/util/makeDataShareable.ts create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 3d2b2bf12..ffcf91cbe 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ src/plugins/pluginList.ts .env config/config.odcPlugin.js yarn-error.log -yarn.lock \ No newline at end of file +# yarn.lock +pnpm-lock.yaml \ No newline at end of file diff --git a/README.md b/README.md index 73804fa5d..b0ab2a1f7 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ pnpm run prepack obclient #### Install Dependencies ```shell -pnpm install +pnpm run install-odc ``` #### Configure ODC Server Address diff --git a/pnpm-lock.yaml b/hidden.yaml similarity index 95% rename from pnpm-lock.yaml rename to hidden.yaml index d602fe2a5..700e02da8 100644 --- a/pnpm-lock.yaml +++ b/hidden.yaml @@ -56,7 +56,11 @@ importers: version: 1.5.3(monaco-editor@0.36.1) '@oceanbase-odc/ob-intl-cli': specifier: ^2.1.3 +<<<<<<< HEAD:hidden.yaml + version: 2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.8.3) +======= version: 2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@oceanbase-odc/ob-parser-js': specifier: ^3.1.2 version: 3.2.0 @@ -106,8 +110,13 @@ importers: specifier: ^3.0.0 version: 3.0.0 '@umijs/max': +<<<<<<< HEAD:hidden.yaml + specifier: ^4.4.12 + version: 4.4.12(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) +======= specifier: ^4.0.66 version: 4.4.12(@babel/core@7.28.0)(@types/node@16.18.126)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)) +>>>>>>> dev-4.4.1:pnpm-lock.yaml adm-zip: specifier: ^0.5.5 version: 0.5.16 @@ -281,7 +290,11 @@ importers: version: 17.0.2(react@17.0.2) react-intl: specifier: ^5.20.10 +<<<<<<< HEAD:hidden.yaml + version: 5.25.1(react@17.0.2)(typescript@5.8.3) +======= version: 5.25.1(react@17.0.2)(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml react-resizable: specifier: ^1.10.1 version: 1.11.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -314,10 +327,17 @@ importers: version: 1.2.2 ts-loader: specifier: 8.4.0 +<<<<<<< HEAD:hidden.yaml + version: 8.4.0(typescript@5.8.3)(webpack@4.47.0) + typescript: + specifier: ^5.0.0 + version: 5.8.3 +======= version: 8.4.0(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)) typescript: specifier: ^5.0.0 version: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml webpack: specifier: ^4.28.0 version: 4.47.0(webpack-cli@3.3.12) @@ -372,6 +392,12 @@ packages: react: '>=16.0.0' react-dom: '>=16.0.0' + '@ant-design/cssinjs@1.24.0': + resolution: {integrity: sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + '@ant-design/fast-color@2.0.6': resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} engines: {node: '>=8.x'} @@ -507,6 +533,10 @@ packages: resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} engines: {node: '>=6.9.0'} + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} + engines: {node: '>=6.9.0'} + '@babel/eslint-parser@7.23.3': resolution: {integrity: sha512-9bTuNlyx7oSstodm1cR1bECj4fkiknsDa1YniISkJemMY3DGhJNYBECbe6QD/q54mp2J8VO66jW3/7uP//iFCw==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} @@ -528,6 +558,10 @@ packages: resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -571,6 +605,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} @@ -619,6 +659,10 @@ packages: resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + engines: {node: '>=6.9.0'} + '@babel/highlight@7.25.9': resolution: {integrity: sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==} engines: {node: '>=6.9.0'} @@ -628,6 +672,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} engines: {node: '>=6.9.0'} @@ -661,7 +710,6 @@ packages: '@babel/plugin-proposal-class-properties@7.18.6': resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. peerDependencies: '@babel/core': ^7.0.0-0 @@ -1197,6 +1245,14 @@ packages: resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.28.3': + resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.28.3': + resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -1205,6 +1261,19 @@ packages: resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} engines: {node: '>=6.9.0'} +<<<<<<< HEAD:hidden.yaml + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} +======= + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} +>>>>>>> dev-4.4.1:pnpm-lock.yaml + engines: {node: '>=6.9.0'} + '@babel/types@7.28.2': resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} @@ -1369,15 +1438,12 @@ packages: '@esbuild-kit/cjs-loader@2.4.4': resolution: {integrity: sha512-NfsJX4PdzhwSkfJukczyUiZGc7zNNWZcEAyqeISpDnn0PTfzMJR1aR8xAIPskBejIxBJbIgCCMzbaYa9SXepIg==} - deprecated: 'Merged into tsx: https://tsx.is' '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} - deprecated: 'Merged into tsx: https://tsx.is' '@esbuild-kit/esm-loader@2.6.5': resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} - deprecated: 'Merged into tsx: https://tsx.is' '@esbuild/aix-ppc64@0.21.4': resolution: {integrity: sha512-Zrm+B33R4LWPLjDEVnEqt2+SLTATlru1q/xYKVn8oVTbiRBGmK2VIMoIYGJDGyftnGaC788IuzGFAlb7IQ0Y8A==} @@ -1683,7 +1749,6 @@ packages: '@floating-ui/react-dom-interactions@0.3.1': resolution: {integrity: sha512-tP2KEh7EHJr5hokSBHcPGojb+AorDNUf0NYfZGg/M+FsMvCOOsSEeEF0O1NDfETIzDnpbHnCs0DuvCFhSMSStg==} - deprecated: Package renamed to @floating-ui/react '@floating-ui/react-dom@0.6.3': resolution: {integrity: sha512-hC+pS5D6AgS2wWjbmSQ6UR6Kpy+drvWGJIri6e1EDGADTPsCaa4KzCgmCczHrQeInx9tqs81EyDmbKJYY2swKg==} @@ -1723,11 +1788,9 @@ packages: '@formatjs/intl-unified-numberformat@3.3.7': resolution: {integrity: sha512-KnWgLRHzCAgT9eyt3OS34RHoyD7dPDYhRcuKn+/6Kv2knDF8Im43J6vlSW6Hm1w63fNq3ZIT1cFk7RuVO3Psag==} - deprecated: We have renamed the package to @formatjs/intl-numberformat '@formatjs/intl-utils@2.3.0': resolution: {integrity: sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==} - deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package '@formatjs/intl@2.2.1': resolution: {integrity: sha512-vgvyUOOrzqVaOFYzTf2d3+ToSkH2JpR7x/4U1RyoHQLmvEaTQvXJ7A2qm1Iy3brGNXC/+/7bUlc3lpH+h/LOJA==} @@ -1743,17 +1806,14 @@ packages: '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead '@humanwhocodes/config-array@0.5.0': resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -1761,11 +1821,9 @@ packages: '@humanwhocodes/object-schema@1.2.1': resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - deprecated: Use @eslint/object-schema instead '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -1817,8 +1875,17 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} +<<<<<<< HEAD:hidden.yaml + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} +======= '@jridgewell/gen-mapping@0.3.12': resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} @@ -1827,11 +1894,28 @@ packages: '@jridgewell/source-map@0.3.10': resolution: {integrity: sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==} +<<<<<<< HEAD:hidden.yaml + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} +======= '@jridgewell/sourcemap-codec@1.5.4': resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} +>>>>>>> dev-4.4.1:pnpm-lock.yaml + + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} '@juggle/resize-observer@3.4.0': resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} @@ -1998,15 +2082,14 @@ packages: '@npmcli/move-file@2.0.1': resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} 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==} peerDependencies: monaco-editor: ~0.38.0 - '@oceanbase-odc/ob-intl-cli@2.1.4': - resolution: {integrity: sha512-AKoGr7cWn3AyvTYB2xoFZtfikBgyRxDWUya1IXbVGZeoBd4GGE7JcwFLmFplYNmtUFrOhyP7Vdkz2SNFAH6gQg==} + '@oceanbase-odc/ob-intl-cli@2.2.0': + resolution: {integrity: sha512-qCJriKatOEwHICu9sGM5b7PYYtWxtVtB1V+P/ZBTwACXQHhQEgFlBCnt9CDAoMIOM9iDEeFvV13VSWt/kGW8ig==} hasBin: true '@oceanbase-odc/ob-parser-js@3.2.0': @@ -2217,24 +2300,25 @@ packages: '@stagewise/toolbar@0.6.2': resolution: {integrity: sha512-WN7PWaOT6YQKjJYL4/85V5UU0eZEws+/UBT/J4wJOEbFxoluLuchqh7xVTmUZTtw0q0xpzlgX8Vb0kAZf/pjmw==} +<<<<<<< HEAD:hidden.yaml + deprecated: 'This package is deprecated and has been replaced by the stagewise CLI. Get started with the CLI here: https://stagewise.io/docs' +======= +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@stylelint/postcss-css-in-js@0.37.3': resolution: {integrity: sha512-scLk3cSH1H9KggSniseb2KNAU5D9FWc3H7BxCSAIdtU9OWIyw0zkEZ9qEKHryRM+SExYXRKNb7tOOVNAsQ3iwg==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: postcss: '>=7.0.0' postcss-syntax: '>=0.36.2' '@stylelint/postcss-css-in-js@0.38.0': resolution: {integrity: sha512-XOz5CAe49kS95p5yRd+DAIWDojTjfmyAQ4bbDlXMdbZTQ5t0ThjSLvWI6JI2uiS7MFurVBkZ6zUqcimzcLTBoQ==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: postcss: '>=7.0.0' postcss-syntax: '>=0.36.2' '@stylelint/postcss-markdown@0.36.2': resolution: {integrity: sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ==} - deprecated: 'Use the original unforked package instead: postcss-markdown' peerDependencies: postcss: '>=7.0.0' postcss-syntax: '>=0.36.2' @@ -2614,6 +2698,11 @@ packages: peerDependencies: '@types/react': ^16.0.0 + '@types/hoist-non-react-statics@3.3.7': + resolution: {integrity: sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==} + peerDependencies: + '@types/react': ^16.0.0 + '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} @@ -3377,7 +3466,6 @@ packages: are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -3682,7 +3770,6 @@ packages: boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. bowser@1.9.4: resolution: {integrity: sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==} @@ -3733,6 +3820,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + browserslist@4.25.3: + resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -3831,6 +3923,9 @@ packages: caniuse-lite@1.0.30001734: resolution: {integrity: sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==} + caniuse-lite@1.0.30001735: + resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==} + caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} @@ -4121,7 +4216,6 @@ packages: copy-concurrently@1.0.5: resolution: {integrity: sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==} - deprecated: This package is no longer supported. copy-descriptor@0.1.1: resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} @@ -4139,6 +4233,9 @@ packages: core-js-pure@3.45.0: resolution: {integrity: sha512-OtwjqcDpY2X/eIIg1ol/n0y/X8A9foliaNt1dSK0gV3J2/zw+89FcNG3mPK+N8YWts4ZFUPxnrAzsxs/lf8yDA==} + core-js-pure@3.45.0: + resolution: {integrity: sha512-OtwjqcDpY2X/eIIg1ol/n0y/X8A9foliaNt1dSK0gV3J2/zw+89FcNG3mPK+N8YWts4ZFUPxnrAzsxs/lf8yDA==} + core-js@3.34.0: resolution: {integrity: sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==} @@ -4695,7 +4792,6 @@ packages: electron-notarize@1.2.2: resolution: {integrity: sha512-ZStVWYcWI7g87/PgjPJSIIhwQXOaw4/XeXU+pWqMMktSLHaGMLHdyPPN7Cmao7+Cr7fYufA16npdtMndYciHNw==} engines: {node: '>= 10.0.0'} - deprecated: Please use @electron/notarize moving forward. There is no API change, just a package name change electron-publish@25.1.7: resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==} @@ -4703,6 +4799,9 @@ packages: electron-to-chromium@1.5.199: resolution: {integrity: sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==} + electron-to-chromium@1.5.207: + resolution: {integrity: sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==} + electron@22.3.27: resolution: {integrity: sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A==} engines: {node: '>= 12.20.55'} @@ -5007,19 +5106,16 @@ packages: eslint@7.32.0: resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} engines: {node: ^10.12.0 || >=12.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@8.35.0: resolution: {integrity: sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true esniff@2.0.1: @@ -5221,7 +5317,6 @@ packages: figgy-pudding@3.5.2: resolution: {integrity: sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==} - deprecated: This module is no longer supported. file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} @@ -5294,13 +5389,24 @@ packages: flatten@1.0.3: resolution: {integrity: sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==} - deprecated: flatten is deprecated in favor of utility frameworks such as lodash. flush-write-stream@1.1.1: resolution: {integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==} follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} +<<<<<<< HEAD:hidden.yaml + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} +======= +>>>>>>> dev-4.4.1:pnpm-lock.yaml engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -5342,6 +5448,10 @@ packages: resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -5395,7 +5505,6 @@ packages: fs-write-stream-atomic@1.0.10: resolution: {integrity: sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==} - deprecated: This package is no longer supported. fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -5427,7 +5536,6 @@ packages: gauge@4.0.4: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -5511,16 +5619,13 @@ packages: glob@7.0.6: resolution: {integrity: sha512-f8c0rE8JiCxpa52kWPAOa3ZaYEnzofDzCQLCn3Vdk0Z5OVLq3BsRFJI4S4ykpeVW6QMGBUkMeUpoEgWnMTnw5Q==} - deprecated: Glob versions prior to v9 are no longer supported glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} @@ -5600,7 +5705,6 @@ packages: har-validator@5.1.5: resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} engines: {node: '>=6'} - deprecated: this library is no longer supported hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} @@ -5881,7 +5985,6 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.1: resolution: {integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==} @@ -5911,7 +6014,6 @@ packages: intl-messageformat-parser@3.6.4: resolution: {integrity: sha512-RgPGwue0mJtoX2Ax8EmMzJzttxjnva7gx0Q7mKJ4oALrTZvtmCeAw5Msz2PcjW4dtCh/h7vN/8GJCxZO1uv+OA==} - deprecated: We've written a new parser that's 6x faster and is backwards compatible. Please use @formatjs/icu-messageformat-parser intl-messageformat@7.8.4: resolution: {integrity: sha512-yS0cLESCKCYjseCOGXuV4pxJm/buTfyCJ1nzQjryHmSehlptbZbn9fnlk1I9peLopZGGbjj46yHHiTAEZ1qOTA==} @@ -6480,8 +6582,13 @@ packages: engines: {node: '>=6'} hasBin: true +<<<<<<< HEAD:hidden.yaml + less@4.4.1: + resolution: {integrity: sha512-X9HKyiXPi0f/ed0XhgUlBeFfxrlDP3xR4M7768Zl+WXLUViuL9AOPPJP4nCV0tgRWvTYvpNmN0SFhZOQzy16PA==} +======= less@4.4.0: resolution: {integrity: sha512-kdTwsyRuncDfjEs0DlRILWNvxhDG/Zij4YLO4TMJgDLW+8OzpfkdPnRgrsRuY1o+oaxJGWsps5f/RVBgGmmN0w==} +>>>>>>> dev-4.4.1:pnpm-lock.yaml engines: {node: '>=14'} hasBin: true @@ -6629,11 +6736,9 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} - deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -6806,7 +6911,6 @@ packages: memoize-one@4.1.0: resolution: {integrity: sha512-2GApq0yI/b22J2j9rhbrAlsHb0Qcz+7yWxeLG8h+95sl1XPUgeLimQSOdur4Vw7cUhrBHwaUZxWFZueojqNRzA==} - deprecated: New custom equality api does not play well with all equality helpers. Please use v5.x memory-fs@0.4.1: resolution: {integrity: sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==} @@ -7017,7 +7121,6 @@ packages: move-concurrently@1.0.1: resolution: {integrity: sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==} - deprecated: This package is no longer supported. ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -7090,7 +7193,6 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} - deprecated: Use your platform's native DOMException instead node-fetch@1.7.3: resolution: {integrity: sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==} @@ -7175,7 +7277,6 @@ packages: npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. nth-check@1.0.2: resolution: {integrity: sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==} @@ -8022,7 +8123,6 @@ packages: querystring@0.2.0: resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} engines: {node: '>=0.4.x'} - deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -8828,7 +8928,6 @@ packages: request@2.88.2: resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} engines: {node: '>= 6'} - deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} @@ -8888,7 +8987,6 @@ packages: resolve-url@0.2.1: resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} - deprecated: https://github.com/lydell/resolve-url#deprecated resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} @@ -8923,12 +9021,10 @@ packages: rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@5.0.1: @@ -9266,18 +9362,15 @@ packages: source-map-resolve@0.5.3: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} - deprecated: See https://github.com/lydell/source-map-resolve#deprecated source-map-resolve@0.6.0: resolution: {integrity: sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==} - deprecated: See https://github.com/lydell/source-map-resolve#deprecated source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} source-map-url@0.4.1: resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==} - deprecated: See https://github.com/lydell/source-map-url#deprecated source-map@0.5.7: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} @@ -9291,6 +9384,10 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + spawn-command@0.0.2: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} @@ -9357,7 +9454,6 @@ packages: stable@0.1.8: resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} - deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -9621,8 +9717,13 @@ packages: engines: {node: '>=10.13.0'} hasBin: true +<<<<<<< HEAD:hidden.yaml + swr@2.3.6: + resolution: {integrity: sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==} +======= swr@2.3.5: resolution: {integrity: sha512-4e7pjTVulZTIL+b/S0RYFsgDcTcXPLUOvBPqyh9YdD+PkHeEMoaPwDmF9Kv6I1nnPg1OFKhiiEYpsYaaE2W2jA==} +>>>>>>> dev-4.4.1:pnpm-lock.yaml peerDependencies: react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -10012,7 +10113,6 @@ packages: urix@0.1.0: resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==} - deprecated: Please see https://github.com/lydell/urix#deprecated url-okam@0.11.1: resolution: {integrity: sha512-AM6OVeZNwKiirK3IwKxHuopgjX1jB0F8srK9OlCXN+wdmTNg6vgnN9xyQ5abhxq8Oj/kTleLU8OCfZ1FaEW37w==} @@ -10090,7 +10190,6 @@ packages: uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true uuid@8.0.0: @@ -10326,13 +10425,11 @@ packages: xterm-addon-fit@0.4.0: resolution: {integrity: sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w==} - deprecated: This package is now deprecated. Move to @xterm/addon-fit instead. peerDependencies: xterm: ^4.0.0 xterm@4.19.0: resolution: {integrity: sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ==} - deprecated: This package is now deprecated. Move to @xterm/xterm instead. y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -10458,6 +10555,18 @@ snapshots: react-dom: 17.0.2(react@17.0.2) stylis: 4.3.6 + '@ant-design/cssinjs@1.24.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@babel/runtime': 7.28.3 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.5.1 + csstype: 3.1.3 + rc-util: 5.44.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + stylis: 4.3.6 + '@ant-design/fast-color@2.0.6': dependencies: '@babel/runtime': 7.28.2 @@ -10493,7 +10602,11 @@ snapshots: '@ant-design/icons': 5.6.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@ant-design/pro-provider': 2.16.2(antd@4.24.16(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) '@ant-design/pro-utils': 2.18.0(antd@4.24.16(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) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) classnames: 2.5.1 rc-resize-observer: 1.3.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -10514,7 +10627,11 @@ snapshots: '@ant-design/pro-skeleton': 2.2.1(antd@4.24.16(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) '@ant-design/pro-table': 3.21.0(antd@4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(rc-field-form@2.7.0(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) '@ant-design/pro-utils': 2.18.0(antd@4.24.16(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) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -10528,7 +10645,11 @@ snapshots: '@ant-design/pro-provider': 2.16.2(antd@4.24.16(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) '@ant-design/pro-skeleton': 2.2.1(antd@4.24.16(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) '@ant-design/pro-utils': 2.18.0(antd@4.24.16(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) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) rc-resize-observer: 0.2.6(react-dom@17.0.2(react@17.0.2))(react@17.0.2) rc-util: 5.44.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -10542,7 +10663,11 @@ snapshots: '@ant-design/icons': 5.6.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@ant-design/pro-provider': 2.16.2(antd@4.24.16(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) '@ant-design/pro-utils': 2.18.0(antd@4.24.16(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) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@chenshuai2144/sketch-color': 1.0.9(react@17.0.2) antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) classnames: 2.5.1 @@ -10551,7 +10676,11 @@ snapshots: lodash-es: 4.17.21 rc-util: 5.44.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 +<<<<<<< HEAD:hidden.yaml + swr: 2.3.6(react@17.0.2) +======= swr: 2.3.5(react@17.0.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml transitivePeerDependencies: - react-dom @@ -10561,7 +10690,11 @@ snapshots: '@ant-design/pro-field': 3.1.0(antd@4.24.16(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) '@ant-design/pro-provider': 2.16.2(antd@4.24.16(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) '@ant-design/pro-utils': 2.18.0(antd@4.24.16(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) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@chenshuai2144/sketch-color': 1.0.9(react@17.0.2) '@umijs/use-params': 1.0.9(react@17.0.2) antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -10581,7 +10714,11 @@ snapshots: '@ant-design/icons': 5.6.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@ant-design/pro-provider': 2.16.2(antd@4.24.16(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) '@ant-design/pro-utils': 2.18.0(antd@4.24.16(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) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@umijs/route-utils': 4.0.1 '@umijs/use-params': 1.0.9(react@17.0.2) antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -10593,7 +10730,11 @@ snapshots: rc-util: 5.44.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) +<<<<<<< HEAD:hidden.yaml + swr: 2.3.6(react@17.0.2) +======= swr: 2.3.5(react@17.0.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml warning: 4.0.3 '@ant-design/pro-list@2.6.10(antd@4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(rc-field-form@2.7.0(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)': @@ -10604,7 +10745,11 @@ snapshots: '@ant-design/pro-field': 3.1.0(antd@4.24.16(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) '@ant-design/pro-table': 3.21.0(antd@4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(rc-field-form@2.7.0(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) '@ant-design/pro-utils': 2.18.0(antd@4.24.16(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) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) classnames: 2.5.1 dayjs: 1.11.13 @@ -10618,18 +10763,30 @@ snapshots: '@ant-design/pro-provider@2.16.2(antd@4.24.16(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: '@ant-design/cssinjs': 1.24.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@ctrl/tinycolor': 3.6.1 antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) dayjs: 1.11.13 rc-util: 5.44.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) +<<<<<<< HEAD:hidden.yaml + swr: 2.3.6(react@17.0.2) + + '@ant-design/pro-skeleton@2.2.1(antd@4.24.16(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: + '@babel/runtime': 7.28.3 +======= swr: 2.3.5(react@17.0.2) '@ant-design/pro-skeleton@2.2.1(antd@4.24.16(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: '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -10643,7 +10800,11 @@ snapshots: '@ant-design/pro-form': 2.32.0(antd@4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(rc-field-form@2.7.0(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) '@ant-design/pro-provider': 2.16.2(antd@4.24.16(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) '@ant-design/pro-utils': 2.18.0(antd@4.24.16(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) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@dnd-kit/core': 6.3.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@dnd-kit/modifiers': 6.0.1(@dnd-kit/core@6.3.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react@17.0.2) '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react@17.0.2) @@ -10663,7 +10824,11 @@ snapshots: dependencies: '@ant-design/icons': 5.6.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@ant-design/pro-provider': 2.16.2(antd@4.24.16(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) +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) classnames: 2.5.1 dayjs: 1.11.13 @@ -10673,7 +10838,11 @@ snapshots: react: 17.0.2 react-dom: 17.0.2(react@17.0.2) safe-stable-stringify: 2.5.0 +<<<<<<< HEAD:hidden.yaml + swr: 2.3.6(react@17.0.2) +======= swr: 2.3.5(react@17.0.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@ant-design/react-slick@1.0.2(react@17.0.2)': dependencies: @@ -10716,6 +10885,15 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 +<<<<<<< HEAD:hidden.yaml + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.23.6) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 +======= '@babel/generator': 7.28.0 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.27.3(@babel/core@7.23.6) @@ -10723,6 +10901,7 @@ snapshots: '@babel/parser': 7.28.0 '@babel/template': 7.27.2 '@babel/traverse': 7.28.0 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@babel/types': 7.28.2 convert-source-map: 2.0.0 debug: 4.4.1 @@ -10752,6 +10931,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.28.3': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/eslint-parser@7.23.3(@babel/core@7.23.6)(eslint@8.35.0)': dependencies: '@babel/core': 7.23.6 @@ -10784,6 +10983,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.28.2 @@ -10843,6 +11050,9 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)': +======= '@babel/helper-module-transforms@7.27.3(@babel/core@7.23.6)': dependencies: '@babel/core': 7.23.6 @@ -10853,6 +11063,7 @@ snapshots: - supports-color '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@babel/core': 7.28.0 '@babel/helper-module-imports': 7.27.1 @@ -10861,6 +11072,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.28.3(@babel/core@7.23.6)': + dependencies: + '@babel/core': 7.23.6 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + '@babel/helper-optimise-call-expression@7.27.1': dependencies: '@babel/types': 7.28.2 @@ -10887,7 +11125,11 @@ snapshots: '@babel/helper-simple-access@7.27.1': dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/traverse': 7.28.3 +======= '@babel/traverse': 7.28.0 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -10918,6 +11160,11 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.28.2 + '@babel/helpers@7.28.3': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + '@babel/highlight@7.25.9': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -10929,7 +11176,15 @@ snapshots: dependencies: '@babel/types': 7.28.2 +<<<<<<< HEAD:hidden.yaml + '@babel/parser@7.28.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.27.4)': +======= '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.0)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@babel/core': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 @@ -11267,8 +11522,13 @@ snapshots: '@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.28.0)': dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/core': 7.27.4 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.27.4) +======= '@babel/core': 7.28.0 '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-simple-access': 7.27.1 transitivePeerDependencies: @@ -11392,6 +11652,16 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 +======= '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -11400,6 +11670,7 @@ snapshots: '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.0)': @@ -11613,6 +11884,10 @@ snapshots: '@babel/runtime@7.28.2': {} + '@babel/runtime@7.28.3': {} + + '@babel/runtime@7.28.3': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -11631,6 +11906,27 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@babel/traverse@7.28.3': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.6': +======= + '@babel/types@7.28.2': +>>>>>>> dev-4.4.1:pnpm-lock.yaml + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.2': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -12109,7 +12405,11 @@ snapshots: '@formatjs/intl-utils@2.3.0': {} +<<<<<<< HEAD:hidden.yaml + '@formatjs/intl@2.2.1(typescript@5.8.3)': +======= '@formatjs/intl@2.2.1(typescript@5.9.2)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@formatjs/ecma402-abstract': 1.11.4 '@formatjs/fast-memoize': 1.2.1 @@ -12119,7 +12419,11 @@ snapshots: intl-messageformat: 9.13.0 tslib: 2.8.1 optionalDependencies: +<<<<<<< HEAD:hidden.yaml + typescript: 5.8.3 +======= typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@gar/promisify@1.1.3': {} @@ -12197,9 +12501,15 @@ snapshots: '@jest/transform@29.7.0': dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/core': 7.28.3 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.30 +======= '@babel/core': 7.28.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.29 +>>>>>>> dev-4.4.1:pnpm-lock.yaml babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -12246,25 +12556,54 @@ snapshots: '@types/yargs': 17.0.33 chalk: 4.1.2 +<<<<<<< HEAD:hidden.yaml + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/gen-mapping@0.3.8': +======= '@jridgewell/gen-mapping@0.3.12': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@jridgewell/sourcemap-codec': 1.5.4 '@jridgewell/trace-mapping': 0.3.29 '@jridgewell/resolve-uri@3.1.2': {} +<<<<<<< HEAD:hidden.yaml + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 +======= '@jridgewell/source-map@0.3.10': dependencies: '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@jridgewell/sourcemap-codec@1.5.4': {} +<<<<<<< HEAD:hidden.yaml + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.25': +======= '@jridgewell/trace-mapping@0.3.29': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@juggle/resize-observer@3.4.0': {} '@loadable/component@5.15.2(react@17.0.2)': @@ -12428,7 +12767,11 @@ snapshots: comlink: 4.4.2 monaco-editor: 0.36.1 +<<<<<<< HEAD:hidden.yaml + '@oceanbase-odc/ob-intl-cli@2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.8.3)': +======= '@oceanbase-odc/ob-intl-cli@2.1.4(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@babel/core': 7.28.0 '@babel/generator': 7.28.0 @@ -12454,9 +12797,15 @@ snapshots: google-translate-api-x: 10.7.2 lodash: 4.17.21 node-fetch: 2.6.7(encoding@0.1.13) +<<<<<<< HEAD:hidden.yaml + prettier-eslint: 16.4.2(typescript@5.8.3) + prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@5.8.3) + prettier-plugin-packagejson: 2.5.15(prettier@2.8.8) +======= prettier-eslint: 16.4.2(typescript@5.9.2) prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@5.9.2) prettier-plugin-packagejson: 2.5.19(prettier@2.8.8) +>>>>>>> dev-4.4.1:pnpm-lock.yaml transitivePeerDependencies: - '@swc/helpers' - '@volar/vue-language-plugin-pug' @@ -12782,7 +13131,11 @@ snapshots: '@stagewise/toolbar@0.6.2': {} +<<<<<<< HEAD:hidden.yaml + '@stylelint/postcss-css-in-js@0.37.3(postcss-syntax@0.36.2(postcss@8.5.6))(postcss@7.0.39)': +======= '@stylelint/postcss-css-in-js@0.37.3(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@7.0.39)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@babel/core': 7.28.0 postcss: 7.0.39 @@ -12807,6 +13160,56 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + + '@svgr/babel-plugin-replace-jsx-attribute-value@6.5.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + + '@svgr/babel-plugin-svg-dynamic-title@6.5.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + + '@svgr/babel-plugin-svg-em-dimensions@6.5.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + + '@svgr/babel-plugin-transform-react-native-svg@6.5.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + + '@svgr/babel-plugin-transform-svg-component@6.5.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + + '@svgr/babel-preset@6.5.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@svgr/babel-plugin-add-jsx-attribute': 6.5.1(@babel/core@7.28.3) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.3) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.3) + '@svgr/babel-plugin-replace-jsx-attribute-value': 6.5.1(@babel/core@7.28.3) + '@svgr/babel-plugin-svg-dynamic-title': 6.5.1(@babel/core@7.28.3) + '@svgr/babel-plugin-svg-em-dimensions': 6.5.1(@babel/core@7.28.3) + '@svgr/babel-plugin-transform-react-native-svg': 6.5.1(@babel/core@7.28.3) + '@svgr/babel-plugin-transform-svg-component': 6.5.1(@babel/core@7.28.3) + + '@svgr/core@6.5.1': + dependencies: + '@babel/core': 7.28.3 + '@svgr/babel-preset': 6.5.1(@babel/core@7.28.3) +======= '@svgr/babel-plugin-add-jsx-attribute@6.5.1(@babel/core@7.28.0)': dependencies: '@babel/core': 7.28.0 @@ -12855,6 +13258,7 @@ snapshots: dependencies: '@babel/core': 7.28.0 '@svgr/babel-preset': 6.5.1(@babel/core@7.28.0) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@svgr/plugin-jsx': 6.5.1(@svgr/core@6.5.1) camelcase: 6.3.0 cosmiconfig: 7.1.0 @@ -12868,8 +13272,13 @@ snapshots: '@svgr/plugin-jsx@6.5.1(@svgr/core@6.5.1)': dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/core': 7.28.3 + '@svgr/babel-preset': 6.5.1(@babel/core@7.28.3) +======= '@babel/core': 7.28.0 '@svgr/babel-preset': 6.5.1(@babel/core@7.28.0) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@svgr/core': 6.5.1 '@svgr/hast-util-to-babel-ast': 6.5.1 svg-parser: 2.0.4 @@ -13013,7 +13422,11 @@ snapshots: '@types/babel__core@7.20.5': dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/parser': 7.28.3 +======= '@babel/parser': 7.28.0 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@babel/types': 7.28.2 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 @@ -13025,7 +13438,11 @@ snapshots: '@types/babel__template@7.4.4': dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/parser': 7.28.3 +======= '@babel/parser': 7.28.0 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@babel/types': 7.28.2 '@types/babel__traverse@7.28.0': @@ -13189,7 +13606,12 @@ snapshots: '@types/history@5.0.0': dependencies: - history: 5.3.0 + history: 4.10.1 + + '@types/hoist-non-react-statics@3.3.7(@types/react@16.14.65)': + dependencies: + '@types/react': 16.14.65 + hoist-non-react-statics: 3.3.2 '@types/hoist-non-react-statics@3.3.7(@types/react@16.14.65)': dependencies: @@ -13274,7 +13696,7 @@ snapshots: '@types/history': 4.7.11 '@types/react': 16.14.65 '@types/react-router': 5.1.20 - redux: 4.2.1 + redux: 3.7.2 '@types/react-router@5.1.20': dependencies: @@ -13343,10 +13765,17 @@ snapshots: '@types/node': 16.18.126 optional: true +<<<<<<< HEAD:hidden.yaml + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@7.32.0)(typescript@4.9.5)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@5.8.3) +======= '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5)': dependencies: '@eslint-community/regexpp': 4.12.1 '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@4.9.5) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@7.32.0)(typescript@4.9.5) '@typescript-eslint/utils': 5.62.0(eslint@7.32.0)(typescript@4.9.5) @@ -13362,6 +13791,15 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@5.8.3) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) +======= '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.9.2))(eslint@8.35.0)(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -13369,15 +13807,22 @@ snapshots: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.35.0)(typescript@5.9.2) '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml debug: 4.4.1 - eslint: 8.35.0 + eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare-lite: 1.4.0 semver: 7.7.2 +<<<<<<< HEAD:hidden.yaml + tsutils: 3.21.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 +======= tsutils: 3.21.0(typescript@5.9.2) optionalDependencies: typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml transitivePeerDependencies: - supports-color @@ -13406,6 +13851,25 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + debug: 4.4.1 + eslint: 8.35.0 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.3) +======= '@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 5.62.0 @@ -13423,11 +13887,16 @@ snapshots: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.1 eslint: 8.57.1 optionalDependencies: +<<<<<<< HEAD:hidden.yaml + typescript: 5.8.3 +======= typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml transitivePeerDependencies: - supports-color @@ -13458,6 +13927,17 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@typescript-eslint/type-utils@5.62.0(eslint@8.35.0)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) + debug: 4.4.1 + eslint: 8.35.0 + tsutils: 3.21.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 +======= '@typescript-eslint/type-utils@5.62.0(eslint@8.35.0)(typescript@5.9.2)': dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.2) @@ -13467,6 +13947,7 @@ snapshots: tsutils: 3.21.0(typescript@5.9.2) optionalDependencies: typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml transitivePeerDependencies: - supports-color @@ -13504,7 +13985,11 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.8.3)': +======= '@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.2)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 @@ -13512,6 +13997,15 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.7.2 +<<<<<<< HEAD:hidden.yaml + tsutils: 3.21.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.8.3)': +======= tsutils: 3.21.0(typescript@5.9.2) optionalDependencies: typescript: 5.9.2 @@ -13519,6 +14013,7 @@ snapshots: - supports-color '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.2)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 @@ -13527,9 +14022,15 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.7.2 +<<<<<<< HEAD:hidden.yaml + ts-api-utils: 1.4.3(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 +======= ts-api-utils: 1.4.3(typescript@5.9.2) optionalDependencies: typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml transitivePeerDependencies: - supports-color @@ -13548,14 +14049,22 @@ snapshots: - supports-color - typescript +<<<<<<< HEAD:hidden.yaml + '@typescript-eslint/utils@5.62.0(eslint@8.35.0)(typescript@5.8.3)': +======= '@typescript-eslint/utils@5.62.0(eslint@8.35.0)(typescript@5.9.2)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.35.0) '@types/json-schema': 7.0.15 '@types/semver': 7.7.0 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 +<<<<<<< HEAD:hidden.yaml + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) +======= '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml eslint: 8.35.0 eslint-scope: 5.1.1 semver: 7.7.2 @@ -13605,10 +14114,17 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@umijs/bundler-mako@0.11.10(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0)': + dependencies: + '@umijs/bundler-utils': 4.4.12 + '@umijs/mako': 0.11.10(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0) +======= '@umijs/bundler-mako@0.11.10(postcss@8.5.6)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12))': dependencies: '@umijs/bundler-utils': 4.4.12 '@umijs/mako': 0.11.10(postcss@8.5.6)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)) +>>>>>>> dev-4.4.1:pnpm-lock.yaml chalk: 4.1.2 compression: 1.8.1 connect-history-api-fallback: 2.0.0 @@ -13662,7 +14178,11 @@ snapshots: - supports-color - terser +<<<<<<< HEAD:hidden.yaml + '@umijs/bundler-webpack@4.4.12(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0)': +======= '@umijs/bundler-webpack@4.4.12(type-fest@0.21.3)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12))': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@svgr/core': 6.5.1 '@svgr/plugin-jsx': 6.5.1(@svgr/core@6.5.1) @@ -13672,12 +14192,20 @@ snapshots: '@umijs/bundler-utils': 4.4.12 '@umijs/case-sensitive-paths-webpack-plugin': 1.0.1 '@umijs/mfsu': 4.4.12 +<<<<<<< HEAD:hidden.yaml + '@umijs/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.14.0)(type-fest@0.21.3)(webpack@4.47.0) +======= '@umijs/react-refresh-webpack-plugin': 0.5.11(react-refresh@0.14.0)(type-fest@0.21.3)(webpack@4.47.0(webpack-cli@3.3.12)) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@umijs/utils': 4.4.12 cors: 2.8.5 css-loader: 6.7.1(webpack@4.47.0(webpack-cli@3.3.12)) es5-imcompatible-versions: 0.1.90 +<<<<<<< HEAD:hidden.yaml + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.8.3)(webpack@4.47.0) +======= fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)) +>>>>>>> dev-4.4.1:pnpm-lock.yaml jest-worker: 29.4.3 lightningcss: 1.22.1 node-libs-browser: 2.2.1 @@ -13748,6 +14276,16 @@ snapshots: '@umijs/fabric@3.0.0': dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/core': 7.27.4 + '@babel/eslint-parser': 7.27.5(@babel/core@7.27.4)(eslint@7.32.0) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.27.4) + '@babel/plugin-proposal-decorators': 7.27.1(@babel/core@7.27.4) + '@babel/preset-env': 7.27.2(@babel/core@7.27.4) + '@babel/preset-react': 7.27.1(@babel/core@7.27.4) + '@babel/preset-typescript': 7.27.1(@babel/core@7.27.4) + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@7.32.0)(typescript@4.9.5) +======= '@babel/core': 7.28.0 '@babel/eslint-parser': 7.28.0(@babel/core@7.28.0)(eslint@7.32.0) '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.28.0) @@ -13756,13 +14294,18 @@ snapshots: '@babel/preset-react': 7.27.1(@babel/core@7.28.0) '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0) '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@4.9.5) chalk: 4.1.2 eslint: 7.32.0 eslint-config-prettier: 8.10.2(eslint@7.32.0) eslint-formatter-pretty: 4.1.0 eslint-plugin-babel: 5.3.1(eslint@7.32.0) +<<<<<<< HEAD:hidden.yaml + eslint-plugin-jest: 24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@4.9.5) +======= eslint-plugin-jest: 24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5) +>>>>>>> dev-4.4.1:pnpm-lock.yaml eslint-plugin-promise: 6.6.0(eslint@7.32.0) eslint-plugin-react: 7.37.5(eslint@7.32.0) eslint-plugin-react-hooks: 4.6.2(eslint@7.32.0) @@ -13791,15 +14334,26 @@ snapshots: '@babel/runtime': 7.23.6 query-string: 6.14.1 +<<<<<<< HEAD:hidden.yaml + '@umijs/lint@4.4.12(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.8.3)': +======= '@umijs/lint@4.4.12(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.9.2)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@babel/core': 7.23.6 '@babel/eslint-parser': 7.23.3(@babel/core@7.23.6)(eslint@8.35.0) '@stylelint/postcss-css-in-js': 0.38.0(postcss-syntax@0.36.2(postcss@8.5.6))(postcss@8.5.6) +<<<<<<< HEAD:hidden.yaml + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@5.8.3) + '@umijs/babel-preset-umi': 4.4.12 + eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3) +======= '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.9.2))(eslint@8.35.0)(typescript@5.9.2) '@typescript-eslint/parser': 5.62.0(eslint@8.35.0)(typescript@5.9.2) '@umijs/babel-preset-umi': 4.4.12 eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.9.2))(eslint@8.35.0)(typescript@5.9.2))(eslint@8.35.0)(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml eslint-plugin-react: 7.33.2(eslint@8.35.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.35.0) postcss: 8.5.6 @@ -13841,21 +14395,34 @@ snapshots: '@umijs/mako-win32-x64-msvc@0.11.10': optional: true +<<<<<<< HEAD:hidden.yaml + '@umijs/mako@0.11.10(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0)': +======= '@umijs/mako@0.11.10(postcss@8.5.6)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12))': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@module-federation/webpack-bundler-runtime': 0.8.12 '@swc/helpers': 0.5.1 '@types/resolve': 1.20.6 chalk: 4.1.2 enhanced-resolve: 5.18.3 +<<<<<<< HEAD:hidden.yaml + less: 4.4.1 + less-loader: 12.3.0(less@4.4.1)(webpack@4.47.0) +======= less: 4.4.0 less-loader: 12.3.0(less@4.4.0)(webpack@4.47.0(webpack-cli@3.3.12)) +>>>>>>> dev-4.4.1:pnpm-lock.yaml loader-runner: 4.3.0 loader-utils: 3.3.1 lodash: 4.17.21 node-libs-browser-okam: 2.2.5 piscina: 4.9.2 +<<<<<<< HEAD:hidden.yaml + postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0) +======= postcss-loader: 8.1.1(postcss@8.5.6)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)) +>>>>>>> dev-4.4.1:pnpm-lock.yaml react-error-overlay: 6.0.9 react-refresh: 0.14.2 resolve: 1.22.10 @@ -13880,6 +14447,16 @@ snapshots: - typescript - webpack +<<<<<<< HEAD:hidden.yaml + '@umijs/max@4.4.12(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0)': + dependencies: + '@umijs/lint': 4.4.12(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.8.3) + '@umijs/plugins': 4.4.12(@babel/core@7.27.4)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(antd@4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(rc-field-form@2.7.0(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) + antd: 4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + eslint: 8.35.0 + stylelint: 14.8.2 + umi: 4.4.12(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) +======= '@umijs/max@4.4.12(@babel/core@7.28.0)(@types/node@16.18.126)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lightningcss@1.22.1)(prettier@2.8.8)(rc-field-form@2.7.0(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)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12))': dependencies: '@umijs/lint': 4.4.12(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.9.2) @@ -13888,6 +14465,7 @@ snapshots: eslint: 8.35.0 stylelint: 14.8.2 umi: 4.4.12(@babel/core@7.28.0)(@types/node@16.18.126)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)) +>>>>>>> dev-4.4.1:pnpm-lock.yaml transitivePeerDependencies: - '@babel/core' - '@rspack/core' @@ -13941,7 +14519,11 @@ snapshots: dependencies: tsx: 3.12.2 +<<<<<<< HEAD:hidden.yaml + '@umijs/plugins@4.4.12(@babel/core@7.27.4)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(antd@4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(rc-field-form@2.7.0(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)': +======= '@umijs/plugins@4.4.12(@babel/core@7.28.0)(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(antd@4.24.16(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(rc-field-form@2.7.0(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)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@ahooksjs/use-request': 2.8.15(react@17.0.2) '@ant-design/antd-theme-variable': 1.0.0 @@ -13986,7 +14568,11 @@ snapshots: - react-native - supports-color +<<<<<<< HEAD:hidden.yaml + '@umijs/preset-umi@4.4.12(@types/node@16.18.126)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0)': +======= '@umijs/preset-umi@4.4.12(@types/node@16.18.126)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12))': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@iconify/utils': 2.1.1 '@stagewise/toolbar': 0.6.2 @@ -13994,10 +14580,17 @@ snapshots: '@umijs/ast': 4.4.12 '@umijs/babel-preset-umi': 4.4.12 '@umijs/bundler-esbuild': 4.4.12 +<<<<<<< HEAD:hidden.yaml + '@umijs/bundler-mako': 0.11.10(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0) + '@umijs/bundler-utils': 4.4.12 + '@umijs/bundler-vite': 4.4.12(@types/node@16.18.126)(lightningcss@1.22.1)(postcss@8.5.6)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1) + '@umijs/bundler-webpack': 4.4.12(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) +======= '@umijs/bundler-mako': 0.11.10(postcss@8.5.6)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)) '@umijs/bundler-utils': 4.4.12 '@umijs/bundler-vite': 4.4.12(@types/node@16.18.126)(lightningcss@1.22.1)(postcss@8.5.6)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1) '@umijs/bundler-webpack': 4.4.12(type-fest@0.21.3)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@umijs/core': 4.4.12 '@umijs/did-you-know': 1.0.4 '@umijs/es-module-parser': 0.0.7 @@ -14096,13 +14689,21 @@ snapshots: transitivePeerDependencies: - supports-color +<<<<<<< HEAD:hidden.yaml + '@umijs/test@4.4.12(@babel/core@7.27.4)': +======= '@umijs/test@4.4.12(@babel/core@7.28.0)': +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.28.0) '@jest/types': 27.5.1 '@umijs/bundler-utils': 4.4.12 '@umijs/utils': 4.4.12 +<<<<<<< HEAD:hidden.yaml + babel-jest: 29.7.0(@babel/core@7.27.4) +======= babel-jest: 29.7.0(@babel/core@7.28.0) +>>>>>>> dev-4.4.1:pnpm-lock.yaml esbuild: 0.21.4 identity-obj-proxy: 3.0.0 isomorphic-unfetch: 4.0.2 @@ -14134,9 +14735,15 @@ snapshots: '@vitejs/plugin-react@4.0.0(vite@4.5.2(@types/node@16.18.126)(less@4.1.3)(lightningcss@1.22.1)(sugarss@2.0.0)(terser@5.43.1))': dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/core': 7.28.3 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.3) +======= '@babel/core': 7.28.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) +>>>>>>> dev-4.4.1:pnpm-lock.yaml react-refresh: 0.14.2 vite: 4.5.2(@types/node@16.18.126)(less@4.1.3)(lightningcss@1.22.1)(sugarss@2.0.0)(terser@5.43.1) transitivePeerDependencies: @@ -14745,8 +15352,13 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.6): dependencies: +<<<<<<< HEAD:hidden.yaml + browserslist: 4.25.3 + caniuse-lite: 1.0.30001735 +======= browserslist: 4.25.2 caniuse-lite: 1.0.30001734 +>>>>>>> dev-4.4.1:pnpm-lock.yaml fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -14885,7 +15497,11 @@ snapshots: - '@babel/core' - supports-color +<<<<<<< HEAD:hidden.yaml + babel-preset-current-node-syntax@1.2.0(@babel/core@7.27.4): +======= babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.0): +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@babel/core': 7.28.0 '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.0) @@ -14908,7 +15524,11 @@ snapshots: dependencies: '@babel/core': 7.28.0 babel-plugin-jest-hoist: 29.6.3 +<<<<<<< HEAD:hidden.yaml + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.27.4) +======= babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.0) +>>>>>>> dev-4.4.1:pnpm-lock.yaml bail@1.0.5: {} @@ -15094,6 +15714,13 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.2) + browserslist@4.25.3: + dependencies: + caniuse-lite: 1.0.30001735 + electron-to-chromium: 1.5.207 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.3) + bser@2.1.1: dependencies: node-int64: 0.4.0 @@ -15265,6 +15892,8 @@ snapshots: caniuse-lite@1.0.30001734: {} + caniuse-lite@1.0.30001735: {} + caseless@0.12.0: {} chalk@1.1.3: @@ -15595,6 +16224,8 @@ snapshots: core-js-pure@3.45.0: {} + core-js-pure@3.45.0: {} + core-js@3.34.0: {} core-js@3.45.0: {} @@ -15616,14 +16247,22 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 +<<<<<<< HEAD:hidden.yaml + cosmiconfig@9.0.0(typescript@5.8.3): +======= cosmiconfig@9.0.0(typescript@5.9.2): +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: +<<<<<<< HEAD:hidden.yaml + typescript: 5.8.3 +======= typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml crc-32@1.2.2: {} @@ -16135,7 +16774,11 @@ snapshots: dva-core@1.5.0-beta.2(redux@3.7.2): dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml flatten: 1.0.3 global: 4.4.0 invariant: 2.2.4 @@ -16146,7 +16789,11 @@ snapshots: dva-core@2.0.4(redux@4.2.1): dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml flatten: 1.0.3 global: 4.4.0 invariant: 2.2.4 @@ -16157,18 +16804,30 @@ snapshots: dva-immer@1.0.2(dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2)): dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml dva: 2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2) immer: 8.0.4 dva-loading@3.0.25(dva-core@2.0.4(redux@4.2.1)): dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml dva-core: 2.0.4(redux@4.2.1) dva@2.5.0-beta.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2): dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@types/isomorphic-fetch': 0.0.34 '@types/react-router-dom': 4.3.5 '@types/react-router-redux': 5.0.27 @@ -16263,6 +16922,8 @@ snapshots: electron-to-chromium@1.5.199: {} + electron-to-chromium@1.5.207: {} + electron@22.3.27: dependencies: '@electron/get': 2.0.3 @@ -16580,22 +17241,39 @@ snapshots: eslint: 7.32.0 eslint-rule-composer: 0.3.0 +<<<<<<< HEAD:hidden.yaml + eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@7.32.0)(typescript@4.9.5): +======= eslint-plugin-jest@24.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5): +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@typescript-eslint/experimental-utils': 4.33.0(eslint@7.32.0)(typescript@4.9.5) eslint: 7.32.0 optionalDependencies: +<<<<<<< HEAD:hidden.yaml + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) +======= '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@4.9.5))(eslint@7.32.0)(typescript@4.9.5) +>>>>>>> dev-4.4.1:pnpm-lock.yaml transitivePeerDependencies: - supports-color - typescript +<<<<<<< HEAD:hidden.yaml + eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3))(eslint@8.35.0)(typescript@5.8.3): + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.8.3) + eslint: 8.35.0 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3) +======= eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.9.2))(eslint@8.35.0)(typescript@5.9.2))(eslint@8.35.0)(typescript@5.9.2): dependencies: '@typescript-eslint/utils': 5.62.0(eslint@8.35.0)(typescript@5.9.2) eslint: 8.35.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.35.0)(typescript@5.9.2))(eslint@8.35.0)(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml transitivePeerDependencies: - supports-color - typescript @@ -17231,6 +17909,11 @@ snapshots: readable-stream: 2.3.8 follow-redirects@1.15.11: {} +<<<<<<< HEAD:hidden.yaml + + follow-redirects@1.15.9: {} +======= +>>>>>>> dev-4.4.1:pnpm-lock.yaml for-each@0.3.5: dependencies: @@ -17245,7 +17928,11 @@ snapshots: forever-agent@0.6.1: {} +<<<<<<< HEAD:hidden.yaml + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.8.3)(webpack@4.47.0): +======= fork-ts-checker-webpack-plugin@8.0.0(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)): +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: '@babel/code-frame': 7.27.1 chalk: 4.1.2 @@ -17259,7 +17946,11 @@ snapshots: schema-utils: 3.3.0 semver: 7.7.2 tapable: 2.2.2 +<<<<<<< HEAD:hidden.yaml + typescript: 5.8.3 +======= typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml webpack: 4.47.0(webpack-cli@3.3.12) form-data@2.3.3: @@ -17285,6 +17976,14 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -17676,7 +18375,11 @@ snapshots: history@4.10.1: dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml loose-envify: 1.4.0 resolve-pathname: 3.0.0 tiny-invariant: 1.3.3 @@ -17871,7 +18574,11 @@ snapshots: import-html-entry@1.17.0: dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml import-lazy@4.0.0: {} @@ -18252,8 +18959,13 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/core': 7.28.3 + '@babel/parser': 7.28.3 +======= '@babel/core': 7.28.0 '@babel/parser': 7.28.0 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -18446,9 +19158,15 @@ snapshots: dependencies: invert-kv: 3.0.1 +<<<<<<< HEAD:hidden.yaml + less-loader@12.3.0(less@4.4.1)(webpack@4.47.0): + dependencies: + less: 4.4.1 +======= less-loader@12.3.0(less@4.4.0)(webpack@4.47.0(webpack-cli@3.3.12)): dependencies: less: 4.4.0 +>>>>>>> dev-4.4.1:pnpm-lock.yaml optionalDependencies: webpack: 4.47.0(webpack-cli@3.3.12) @@ -18470,7 +19188,11 @@ snapshots: needle: 3.3.1 source-map: 0.6.1 +<<<<<<< HEAD:hidden.yaml + less@4.4.1: +======= less@4.4.0: +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: copy-anything: 2.0.6 parse-node-version: 1.0.1 @@ -19743,9 +20465,15 @@ snapshots: dependencies: postcss: 8.5.6 +<<<<<<< HEAD:hidden.yaml + postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.8.3)(webpack@4.47.0): + dependencies: + cosmiconfig: 9.0.0(typescript@5.8.3) +======= postcss-loader@8.1.1(postcss@8.5.6)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)): dependencies: cosmiconfig: 9.0.0(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml jiti: 1.21.7 postcss: 8.5.6 semver: 7.7.2 @@ -19826,7 +20554,11 @@ snapshots: '@csstools/postcss-stepped-value-functions': 1.0.1(postcss@8.5.6) '@csstools/postcss-unset-value': 1.0.2(postcss@8.5.6) autoprefixer: 10.4.21(postcss@8.5.6) +<<<<<<< HEAD:hidden.yaml + browserslist: 4.25.3 +======= browserslist: 4.25.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml css-blank-pseudo: 3.0.3(postcss@8.5.6) css-has-pseudo: 3.0.4(postcss@8.5.6) css-prefers-color-scheme: 6.0.3(postcss@8.5.6) @@ -19910,7 +20642,7 @@ snapshots: lodash: 4.17.21 postcss: 8.5.6 - 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-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): dependencies: postcss: 7.0.39 optionalDependencies: @@ -19937,9 +20669,15 @@ snapshots: prelude-ls@1.2.1: {} +<<<<<<< HEAD:hidden.yaml + prettier-eslint@16.4.2(typescript@5.8.3): + dependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.3) +======= prettier-eslint@16.4.2(typescript@5.9.2): dependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml common-tags: 1.8.2 dlv: 1.1.3 eslint: 8.57.1 @@ -19960,10 +20698,17 @@ snapshots: prettier: 2.8.8 typescript: 4.9.5 +<<<<<<< HEAD:hidden.yaml + prettier-plugin-organize-imports@3.2.4(prettier@2.8.8)(typescript@5.8.3): + dependencies: + prettier: 2.8.8 + typescript: 5.8.3 +======= prettier-plugin-organize-imports@3.2.4(prettier@2.8.8)(typescript@5.9.2): dependencies: prettier: 2.8.8 typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml prettier-plugin-packagejson@2.4.3(prettier@2.8.8): dependencies: @@ -20092,7 +20837,11 @@ snapshots: qiankun@2.10.16: dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml import-html-entry: 1.17.0 lodash: 4.17.21 single-spa: 5.9.5 @@ -20498,7 +21247,11 @@ snapshots: rc-resize-observer@0.2.6(react-dom@17.0.2(react@17.0.2))(react@17.0.2): dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml classnames: 2.5.1 rc-util: 5.44.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 @@ -20884,11 +21637,19 @@ snapshots: transitivePeerDependencies: - '@types/react' +<<<<<<< HEAD:hidden.yaml + react-intl@5.25.1(react@17.0.2)(typescript@5.8.3): + dependencies: + '@formatjs/ecma402-abstract': 1.11.4 + '@formatjs/icu-messageformat-parser': 2.1.0 + '@formatjs/intl': 2.2.1(typescript@5.8.3) +======= react-intl@5.25.1(react@17.0.2)(typescript@5.9.2): dependencies: '@formatjs/ecma402-abstract': 1.11.4 '@formatjs/icu-messageformat-parser': 2.1.0 '@formatjs/intl': 2.2.1(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@formatjs/intl-displaynames': 5.4.3 '@formatjs/intl-listformat': 6.5.3 '@types/hoist-non-react-statics': 3.3.7(@types/react@16.14.65) @@ -20898,7 +21659,11 @@ snapshots: react: 17.0.2 tslib: 2.8.1 optionalDependencies: +<<<<<<< HEAD:hidden.yaml + typescript: 5.8.3 +======= typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml react-is@16.13.1: {} @@ -20912,7 +21677,11 @@ snapshots: react-redux@5.1.2(react@17.0.2)(redux@3.7.2): dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml hoist-non-react-statics: 3.3.2 invariant: 2.2.4 loose-envify: 1.4.0 @@ -20924,7 +21693,11 @@ snapshots: react-redux@8.1.3(@types/react-dom@16.9.25(@types/react@16.14.65))(@types/react@16.14.65)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(redux@4.2.1): dependencies: +<<<<<<< HEAD:hidden.yaml + '@babel/runtime': 7.28.3 +======= '@babel/runtime': 7.28.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml '@types/hoist-non-react-statics': 3.3.7(@types/react@16.14.65) '@types/use-sync-external-store': 0.0.3 hoist-non-react-statics: 3.3.2 @@ -21765,6 +22538,8 @@ snapshots: source-map@0.7.6: {} + source-map@0.7.6: {} + spawn-command@0.0.2: {} spdx-correct@3.2.0: @@ -22107,7 +22882,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-less@3.1.4)(postcss-scss@2.1.1)(postcss@7.0.39) + 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-value-parser: 4.2.0 resolve-from: 5.0.0 slash: 3.0.0 @@ -22226,7 +23001,11 @@ snapshots: picocolors: 1.1.1 stable: 0.1.8 +<<<<<<< HEAD:hidden.yaml + swr@2.3.6(react@17.0.2): +======= swr@2.3.5(react@17.0.2): +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: dequal: 2.0.3 react: 17.0.2 @@ -22301,7 +23080,11 @@ snapshots: terser@5.43.1: dependencies: +<<<<<<< HEAD:hidden.yaml + '@jridgewell/source-map': 0.3.11 +======= '@jridgewell/source-map': 0.3.10 +>>>>>>> dev-4.4.1:pnpm-lock.yaml acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -22420,6 +23203,15 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 +<<<<<<< HEAD:hidden.yaml + ts-api-utils@1.4.3(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + ts-is-present@1.2.2: {} + + ts-loader@8.4.0(typescript@5.8.3)(webpack@4.47.0): +======= ts-api-utils@1.4.3(typescript@5.9.2): dependencies: typescript: 5.9.2 @@ -22427,13 +23219,18 @@ snapshots: ts-is-present@1.2.2: {} ts-loader@8.4.0(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)): +>>>>>>> dev-4.4.1:pnpm-lock.yaml dependencies: chalk: 4.1.2 enhanced-resolve: 4.5.0 loader-utils: 2.0.4 micromatch: 4.0.8 semver: 7.7.2 +<<<<<<< HEAD:hidden.yaml + typescript: 5.8.3 +======= typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml webpack: 4.47.0(webpack-cli@3.3.12) tslib@1.14.1: {} @@ -22447,10 +23244,17 @@ snapshots: tslib: 1.14.1 typescript: 4.9.5 +<<<<<<< HEAD:hidden.yaml + tsutils@3.21.0(typescript@5.8.3): + dependencies: + tslib: 1.14.1 + typescript: 5.8.3 +======= tsutils@3.21.0(typescript@5.9.2): dependencies: tslib: 1.14.1 typescript: 5.9.2 +>>>>>>> dev-4.4.1:pnpm-lock.yaml tsx@3.12.2: dependencies: @@ -22537,6 +23341,21 @@ snapshots: uc.micro@1.0.6: {} +<<<<<<< HEAD:hidden.yaml + umi@4.4.12(@babel/core@7.27.4)(@types/node@16.18.126)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0): + dependencies: + '@babel/runtime': 7.23.6 + '@umijs/bundler-utils': 4.4.12 + '@umijs/bundler-webpack': 4.4.12(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) + '@umijs/core': 4.4.12 + '@umijs/lint': 4.4.12(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.8.3) + '@umijs/preset-umi': 4.4.12(@types/node@16.18.126)(@types/react@16.14.65)(lightningcss@1.22.1)(rollup@3.29.5)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.8.3)(webpack@4.47.0) + '@umijs/renderer-react': 4.4.12(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@umijs/server': 4.4.12 + '@umijs/test': 4.4.12(@babel/core@7.27.4) + '@umijs/utils': 4.4.12 + prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@5.8.3) +======= umi@4.4.12(@babel/core@7.28.0)(@types/node@16.18.126)(@types/react@16.14.65)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@2.8.8)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(rollup@3.29.5)(stylelint@14.8.2)(sugarss@2.0.0)(terser@5.43.1)(type-fest@0.21.3)(typescript@5.9.2)(webpack@4.47.0(webpack-cli@3.3.12)): dependencies: '@babel/runtime': 7.23.6 @@ -22550,6 +23369,7 @@ snapshots: '@umijs/test': 4.4.12(@babel/core@7.28.0) '@umijs/utils': 4.4.12 prettier-plugin-organize-imports: 3.2.4(prettier@2.8.8)(typescript@5.9.2) +>>>>>>> dev-4.4.1:pnpm-lock.yaml prettier-plugin-packagejson: 2.4.3(prettier@2.8.8) transitivePeerDependencies: - '@babel/core' @@ -22676,6 +23496,12 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + update-browserslist-db@1.1.3(browserslist@4.25.3): + dependencies: + browserslist: 4.25.3 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 diff --git a/package.json b/package.json index 75b591292..499a470fb 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "name": "odc" }, "scripts": { + "install-odc": "node ./scripts/rename.js && pnpm install --registry=https://registry.npmmirror.com", "analyze": "ANALYZE=1 cross-env NODE_OPTIONS=--max_old_space_size=8192 max build", "build-main-dev": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=development webpack -w --config ./build/webpack.main.config.js", "build-main-prod": "cross-env NODE_OPTIONS=--max_old_space_size=8192 NODE_ENV=production webpack --config ./build/webpack.main.prod.config.js", @@ -23,7 +24,7 @@ "dev:client": "concurrently \"UMI_ENV=client npm run dev\" \"npm run build-main-dev\"", "postinstall": "node scripts/plugin/initPlugins.js && max setup", "oic:clear": "oic --configPath ./scripts/must.js --clear", - "oic:extract": "oic --configPath ./scripts/must.js --extract all", + "oic:extract": "oic --configPath ./scripts/must.js --parallel --extract all", "oic:migrate": "oic --configPath ./scripts/must.js --migrate", "prepack": "node ./scripts/clientDependencies/index.js", "pack-client:all": "node ./scripts/client/build.js all", @@ -93,7 +94,7 @@ "@types/react-virtualized": "^9.21.21", "@types/request": "^2.48.7", "@umijs/fabric": "^3.0.0", - "@umijs/max": "^4.0.66", + "@umijs/max": "^4.4.12", "adm-zip": "^0.5.5", "ahooks": "^2.9.0", "ali-oss": "^6.17.1", diff --git a/scripts/clientDependencies/pullJar.js b/scripts/clientDependencies/pullJar.js index 25006f807..781d3d76f 100644 --- a/scripts/clientDependencies/pullJar.js +++ b/scripts/clientDependencies/pullJar.js @@ -20,13 +20,33 @@ const jarUrl = `odc-build/${pkg.version}/jar/odc-slim.jar`; const pluginUrl = `odc-build/${pkg.version}/plugins`; const startersUrl = `odc-build/${pkg.version}/starters`; const modulesUrl = `odc-build/${pkg.version}/modules`; -const { oss } = require('./util'); +const { oss, download } = require('./util'); const isSkipJar = process.env.ODC_BUILD_SKIP_JAR; +const baseUrl = "https://odc-front.oss-cn-beijing.aliyuncs.com/"; + exports.run = async function () { if (isSkipJar) { return true; } + const [isSuccess1, isSuccess2] = await Promise.all([ + download( + baseUrl + `library/h2/h2-v1.jar`, + 'libraries/script', + 'h2-v1.jar', + ), + download( + baseUrl + `library/h2/h2-v2.jar`, + 'libraries/script', + 'h2-v2.jar', + ) + ]) + if (!isSuccess1 || !isSuccess2) { + process.exit(1); + } + console.log('h2-v1.jar and h2-v2.jar download success') + + const plugins = await oss.getOSSFolderFiles(pluginUrl) console.log(plugins) for (let plugin of plugins) { diff --git a/scripts/rename.js b/scripts/rename.js new file mode 100644 index 000000000..1570439ce --- /dev/null +++ b/scripts/rename.js @@ -0,0 +1,25 @@ +/** + * 把假的文件先删除,然后创建一个link到hidden.yaml文件 + */ + + +const path = require('path'); +const fs = require('fs'); + +// 参数 preinstall 和 postinstall +// preinstall 代表install +// postinstall 代表install完成之后 + +const lockFilePath = path.join(process.cwd(), 'hidden.yaml'); + +const fakeLockFilePath = path.join(process.cwd(), 'pnpm-lock.yaml'); +console.log('init install lock file') + +if (fs.existsSync(fakeLockFilePath)) { + fs.unlinkSync(fakeLockFilePath); +} + +fs.symlinkSync(lockFilePath, fakeLockFilePath, 'file'); + + + diff --git a/src/common/network/task.ts b/src/common/network/task.ts index 2b764795e..74b701e11 100644 --- a/src/common/network/task.ts +++ b/src/common/network/task.ts @@ -57,6 +57,7 @@ import { IImportTaskResult, IScheduleTaskImportRequest, IScheduleTerminateCmd, + ITaskTerminateCmd, IScheduleTerminateResult, } from '@/d.ts/importTask'; import odc from '@/plugins/odc'; @@ -779,9 +780,9 @@ export async function getScheduleImportLog(importTaskId: string): Promise { +export async function cancelFlowInstance(data: ITaskTerminateCmd): Promise { const res = await request.post(`/api/v2/flow/flowInstances/asyncCancel`, { - data: flowInstanceId, + data, }); return res?.data; } diff --git a/src/component/Crontab/utils.ts b/src/component/Crontab/utils.ts index 48ece40e2..80d48864b 100644 --- a/src/component/Crontab/utils.ts +++ b/src/component/Crontab/utils.ts @@ -107,6 +107,7 @@ const CronInputName2cronSpeedLabelMap = { }; const reg = /[*?]/; +const intervalReg = /\*\/\d+/; const everyReg = /[*]/; const charsReg = /[#L]/; @@ -482,7 +483,7 @@ class Translator { return nodes ?.map((node) => { let str = ''; - if (reg?.test(node.value)) { + if (reg?.test(node.value) && !intervalReg.test(node.value)) { if (everyReg?.test(node.value)) { return (str = getCronLabel(name as CronInputName, node.value)); } diff --git a/src/component/ODCSetting/config/common.tsx b/src/component/ODCSetting/config/common.tsx index 36cf039cf..2b62986df 100644 --- a/src/component/ODCSetting/config/common.tsx +++ b/src/component/ODCSetting/config/common.tsx @@ -3,6 +3,7 @@ import CheckboxItem from '../Item/CheckboxItem'; import RadioItem from '../Item/RadioItem'; import { ODCSettingGroup } from '../config'; import { resultSetsGroup } from '../utils/configHelper'; +import { isClient } from '@/util/env'; export const getExecutionStrategyConfig = (taskGroup: ODCSettingGroup) => { return [ @@ -26,13 +27,15 @@ export const getExecutionStrategyConfig = (taskGroup: ODCSettingGroup) => { }), value: 'AUTO', }, - { - label: formatMessage({ - id: 'src.component.ODCSetting.config.11DE5799', - defaultMessage: '定时执行', - }), - value: 'TIMER', - }, + !isClient() + ? { + label: formatMessage({ + id: 'src.component.ODCSetting.config.11DE5799', + defaultMessage: '定时执行', + }), + value: 'TIMER', + } + : null, { label: formatMessage({ id: 'src.component.ODCSetting.config.7CE2AC8D', @@ -40,7 +43,7 @@ export const getExecutionStrategyConfig = (taskGroup: ODCSettingGroup) => { }), value: 'MANUAL', }, - ]} + ]?.filter(Boolean)} value={value} onChange={onChange} /> diff --git a/src/component/SQLConfig/index.tsx b/src/component/SQLConfig/index.tsx index 8238bad50..bdffe69cc 100644 --- a/src/component/SQLConfig/index.tsx +++ b/src/component/SQLConfig/index.tsx @@ -43,7 +43,6 @@ const SQLConfig: React.FC = function (props) { const [visible, setVisible] = useState(false); const [showMaxLimit, setShowMaxLimit] = useState(false); const queryLimit = session?.params?.queryLimit; - const maxQueryLimit = Number(setting.getSpaceConfigByKey('odc.sqlexecute.default.maxQueryLimit')); const tableColumnInfoVisible = session?.params.tableColumnInfoVisible; const fullLinkTraceEnabled = session?.params.fullLinkTraceEnabled; const continueExecutionOnError = session?.params.continueExecutionOnError; @@ -60,6 +59,7 @@ const SQLConfig: React.FC = function (props) { /** * 判断是否允许无限制,假如不允许,禁止删除 */ + const maxQueryLimit = session?.params?.maxQueryLimit; if (maxQueryLimit !== Number.MAX_SAFE_INTEGER && !queryLimitValue) { setQueryLimitValue(session?.params.queryLimit); return; @@ -165,7 +165,7 @@ const SQLConfig: React.FC = function (props) { defaultMessage: '不超过查询条数上限', })} - {maxQueryLimit} + {session?.params.maxQueryLimit || '-'} )} diff --git a/src/component/Schedule/components/SchduleExecutionMethodForm/index.tsx b/src/component/Schedule/components/SchduleExecutionMethodForm/index.tsx index bac452a05..ea17c0931 100644 --- a/src/component/Schedule/components/SchduleExecutionMethodForm/index.tsx +++ b/src/component/Schedule/components/SchduleExecutionMethodForm/index.tsx @@ -7,6 +7,7 @@ import Crontab from '@/component/Crontab'; import { disabledDate, disabledTime } from '@/util/utils'; import { forwardRef } from 'react'; import { ICrontab } from '@/component/Crontab/interface'; +import ExecuteFailTip from '@/component/Task/component/ExecuteFailTip'; interface TriggerStrategyFormProps { crontab?: ICrontab; @@ -76,9 +77,12 @@ const SchduleExecutionMethodForm = forwardRef< } if (triggerStrategy === TaskExecStrategy.TIMER) { return ( - - - + <> + + + + + ); } return null; diff --git a/src/component/Task/component/AsyncTaskOperationButton/SubmitTripartiteTaskButton.tsx b/src/component/Task/component/AsyncTaskOperationButton/SubmitTripartiteTaskButton.tsx index b429480c8..d310082e6 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/SubmitTripartiteTaskButton.tsx +++ b/src/component/Task/component/AsyncTaskOperationButton/SubmitTripartiteTaskButton.tsx @@ -26,6 +26,7 @@ import { IBatchTerminateFlowResult, IScheduleTerminateCmd, IScheduleTerminateResult, + ITaskTerminateCmd, } from '@/d.ts/importTask'; const SubmitTripartiteTaskButton = (props: { @@ -213,11 +214,14 @@ const SubmitTripartiteTaskButton = (props: { api: (terminateId: number) => { return getBatchCancelResult(terminateId?.toString?.()); }, - submitApi: (flowInstanceId: number[]) => { - return cancelFlowInstance(flowInstanceId); + submitApi: (data: ITaskTerminateCmd) => { + return cancelFlowInstance(data); }, getSubmitParams: () => { - return props.tasks?.map((item) => item.id); + return { + flowInstanceIds: props.tasks?.map((item) => item.id), + taskType: props.tasks?.[0]?.type, + }; }, isTaskCompleted: (result: IBatchTerminateFlowResult[]) => { return result?.length > 0 && result?.every((i) => i?.terminateSucceed || i?.failReason); diff --git a/src/component/Task/component/AsyncTaskOperationButton/helper.tsx b/src/component/Task/component/AsyncTaskOperationButton/helper.tsx index 90e46ff91..deafd0975 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/helper.tsx +++ b/src/component/Task/component/AsyncTaskOperationButton/helper.tsx @@ -6,7 +6,14 @@ import { } from '@/d.ts/migrateTask'; import { IAsyncTaskOperationConfig } from '.'; import { Popover, Space, Tooltip, Typography } from 'antd'; -import { IConnection, TaskDetail, TaskRecordParameters, TaskStatus, TaskType } from '@/d.ts'; +import { + IConnection, + TaskDetail, + TaskRecord, + TaskRecordParameters, + TaskStatus, + TaskType, +} from '@/d.ts'; import { getLocalFormatDateTime } from '@/util/utils'; import { status as TaskStatusMap, @@ -25,7 +32,12 @@ import { SchedulestatusThatCanBeTerminate, } from '@/constant/triangularization'; import { TaskTypeMap } from '../TaskTable/const'; -import { ScheduleStatus, ScheduleType } from '@/d.ts/schedule'; +import { + IScheduleRecord, + ScheduleRecordParameters, + ScheduleStatus, + ScheduleType, +} from '@/d.ts/schedule'; export const DatabasePopover: React.FC<{ connection: Partial; @@ -480,19 +492,30 @@ export const isScheduleMigrateTask = (scheduleType: ScheduleType) => { ]?.includes(scheduleType); }; -// 是否是在正常调度状态的任务(已创建, 已启用, 已禁用) -export const checkIsScheduleTaskListCanBeExported = (taskStatus: TaskStatus) => { - return scheduleStatusThatCanBeExport?.includes(taskStatus); +// 是否是能被导出的任务状态 +export const checkIsScheduleTaskListCanBeExported = ( + task: IScheduleRecord, +) => { + return scheduleStatusThatCanBeExport?.includes(task?.status); }; // 是否是能终止的任务状态 -export const checkIsTaskListCanBeTerminated = (taskStatus: TaskStatus) => { - return taskStatusThatCanBeTerminate?.includes(taskStatus); +export const checkIsTaskListCanBeTerminated = (task: TaskRecord) => { + return taskStatusThatCanBeTerminate?.includes(task?.status); }; // 是否是能终止的作业状态 -const checkIsScheduleTaskListCanBeTerminated = (scheduleStatus: ScheduleStatus) => { - return SchedulestatusThatCanBeTerminate?.includes(scheduleStatus); +const checkIsScheduleTaskListCanBeTerminated = ( + schedule: IScheduleRecord, +) => { + // 分区计划执行成功能够被终止 + if ( + schedule?.type === ScheduleType.PARTITION_PLAN && + schedule?.status === ScheduleStatus.COMPLETED + ) { + return true; + } + return SchedulestatusThatCanBeTerminate?.includes(schedule?.status); }; /** * 从 URL 中提取 filename 参数的值 diff --git a/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts b/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts index 201a89d55..0f32345fd 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts +++ b/src/component/Task/component/AsyncTaskOperationButton/hooks/useTaskTable.ts @@ -2,7 +2,7 @@ import { useState } from 'react'; import type { AsyncTaskType, ISwitchOdcTaskListResponse } from '@/d.ts/migrateTask'; import { UnfinishedScheduleListType } from '@/d.ts/migrateTask'; import { TaskRecord, TaskRecordParameters, TaskStatus } from '@/d.ts'; -import { ScheduleStatus } from '@/d.ts/schedule'; +import { IScheduleRecord, ScheduleRecordParameters, ScheduleStatus } from '@/d.ts/schedule'; export interface AsyncTaskModalConfig { asyncTaskType: AsyncTaskType; @@ -18,7 +18,9 @@ export interface AsyncTaskModalConfig { modalTitle: string; modalExtra: (count: number, ids?: number[]) => React.ReactNode; - checkStatus: (status: TaskStatus | ScheduleStatus) => boolean; + checkStatus: ( + task: TaskRecord | IScheduleRecord, + ) => boolean; checkStatusFailed: string; onReload: () => void; diff --git a/src/component/Task/component/AsyncTaskOperationButton/index.tsx b/src/component/Task/component/AsyncTaskOperationButton/index.tsx index 7dd616938..0e286302a 100644 --- a/src/component/Task/component/AsyncTaskOperationButton/index.tsx +++ b/src/component/Task/component/AsyncTaskOperationButton/index.tsx @@ -104,7 +104,7 @@ export function AsyncTaskOperationButton(props: IAsyncTaskOperationConfig) { ); return; } - if (props?.dataSource?.find((d) => !props?.checkStatus?.(d?.status))) { + if (props?.dataSource?.find((d) => !props?.checkStatus?.(d))) { message.info(props?.checkStatusFailed); return; } @@ -113,6 +113,12 @@ export function AsyncTaskOperationButton(props: IAsyncTaskOperationConfig) { showModal(); }; + const cancel = () => { + setVisible(false); + setRiskConfirmed(false); + setConfirmRiskUnFinished(false); + }; + return ( <> @@ -157,14 +163,14 @@ export function AsyncTaskOperationButton(props: IAsyncTaskOperationConfig) { )} - setVisible(false)} + closeModal={cancel} disabled={taskListThatCanBeAction?.length === 0} tasks={taskListThatCanBeAction} asyncTaskType={props.asyncTaskType} diff --git a/src/component/Task/component/DataTransferModal/index.tsx b/src/component/Task/component/DataTransferModal/index.tsx index f10a4685f..2d345f85e 100644 --- a/src/component/Task/component/DataTransferModal/index.tsx +++ b/src/component/Task/component/DataTransferModal/index.tsx @@ -27,9 +27,9 @@ import styles from './index.less'; import ObjTable from './ObjTables'; import { getImportTypeLabel } from '@/component/Task/modals/ImportTask/CreateModal/ImportForm/helper'; import { getTaskExecStrategyMap } from '@/component/Task/const'; +import EllipsisText from '@/component/EllipsisText'; import { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; import DatabaseLabel from '@/component/Task/component/DatabaseLabel'; -import EllipsisText from '@/component/EllipsisText'; import login from '@/store/login'; const SimpleTextItem: React.FC<{ @@ -618,6 +618,12 @@ class TaskContent extends React.Component { {this.renderExt(isImport)} <> + +
{isImport @@ -654,7 +660,7 @@ class TaskContent extends React.Component { diff --git a/src/component/Task/component/DatabaseSelect/index.tsx b/src/component/Task/component/DatabaseSelect/index.tsx index 6f70a4045..3f3b0cd00 100644 --- a/src/component/Task/component/DatabaseSelect/index.tsx +++ b/src/component/Task/component/DatabaseSelect/index.tsx @@ -34,10 +34,16 @@ interface IProps { dataSourceId?: number; filters?: ISessionDropdownFiltersProps; extra?: string; - width?: string; + width?: number; placeholder?: string; isLogicalDatabase?: boolean; onChange?: (v: number, database?: IDatabase) => void; + showProject?: boolean; + validateStatus?: 'warning' | 'error' | 'success' | 'validating' | undefined; + help?: string; + style?: React.CSSProperties; + popoverWidth?: number; + manageLinkVisible?: boolean; onInit?: (database?: IDatabase) => void; rules?: Rule[]; } @@ -59,6 +65,12 @@ const DatabaseSelect: React.FC = (props) => { disabled = false, isLogicalDatabase = false, onChange, + showProject = true, + validateStatus, + help, + style, + popoverWidth, + manageLinkVisible = false, onInit, rules, } = props; @@ -68,6 +80,9 @@ const DatabaseSelect: React.FC = (props) => { label={label} name={name} required + validateStatus={validateStatus} + help={help} + style={style} rules={ rules || [ { @@ -92,6 +107,9 @@ const DatabaseSelect: React.FC = (props) => { onChange={onChange} isLogicalDatabase={isLogicalDatabase} placeholder={placeholder} + showProject={showProject} + popoverWidth={popoverWidth} + manageLinkVisible={manageLinkVisible} /> ); diff --git a/src/component/Task/component/ExecuteFailTip/index.tsx b/src/component/Task/component/ExecuteFailTip/index.tsx new file mode 100644 index 000000000..806bc7b51 --- /dev/null +++ b/src/component/Task/component/ExecuteFailTip/index.tsx @@ -0,0 +1,17 @@ +import { formatMessage } from '@/util/intl'; +import { Alert } from 'antd'; + +export default function ExecuteFailTip() { + return ( + + ); +} diff --git a/src/component/Task/component/ImportModal/DatabaseChangeItem.tsx b/src/component/Task/component/ImportModal/DatabaseChangeItem.tsx new file mode 100644 index 000000000..181957151 --- /dev/null +++ b/src/component/Task/component/ImportModal/DatabaseChangeItem.tsx @@ -0,0 +1,57 @@ +import { formatMessage } from '@/util/intl'; +import { Form } from 'antd'; +import DatabaseSelect from '../DatabaseSelect'; +import { TaskType } from '@/d.ts'; +import styles from './index.less'; +import { useState } from 'react'; + +const DatabaseChangeItem = ({ + defaultDatabaseId, + projectId, + onChange, +}: { + defaultDatabaseId: number; + projectId: number; + onChange?: (databaseId: number) => void; +}) => { + const [form] = Form.useForm(); + const [isChanged, setIsChanged] = useState(false); + + return ( +
{ + onChange?.(values.databaseId); + setIsChanged(true); + }} + className={isChanged ? styles.changeDatabaseInput : ''} + > + + + ); +}; + +export default DatabaseChangeItem; diff --git a/src/component/Task/component/ImportModal/DatabaseInfoPopover.tsx b/src/component/Task/component/ImportModal/DatabaseInfoPopover.tsx new file mode 100644 index 000000000..622e37368 --- /dev/null +++ b/src/component/Task/component/ImportModal/DatabaseInfoPopover.tsx @@ -0,0 +1,137 @@ +import { IImportDatabaseView } from '@/d.ts/importTask'; +import { formatMessage } from '@/util/intl'; +import Icon, { InfoCircleOutlined } from '@ant-design/icons'; +import { getDataSourceStyleByConnectType } from '@/common/datasource'; +import { getCloudProviderName } from '../AsyncTaskOperationButton/helper'; +import { fromODCPRoviderToProvider } from '@/d.ts/migrateTask'; +import { ConnectTypeText } from '@/constant/label'; +import { + Tooltip, + Popover, + Table, + Descriptions, + Typography, + Empty, + Radio, + Space, + Checkbox, + Flex, + Tag, +} from 'antd'; +import { haveOCP } from '@/util/env'; + +const DatabaseInfoPopover = ({ + title, + value, + popoverWidth, + children, +}: { + title: string; + value: IImportDatabaseView; + popoverWidth: number; + children: React.JSX.Element; +}) => { + const items = [ + { + key: 'datasource', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.A8BC98CA', + defaultMessage: '数据源', + }), + children: value?.name, + }, + { + key: 'databaseName', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.0B1F9DCD', + defaultMessage: '数据库', + }), + children: value?.databaseName, + }, + { + key: 'type', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.263EFA83', + defaultMessage: '类型', + }), + children: ( +
+ + + {ConnectTypeText(value?.type)} +
+ ), + }, + ...(haveOCP() + ? [ + { + key: 'instanceId', + label: formatMessage({ + id: 'odc.component.ConnectionPopover.InstanceIdTenantId', + defaultMessage: '实例ID/租户ID:', + }), + children: `${value?.instanceId}/${value?.tenantId}`, + }, + ] + : [ + { + key: 'host', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.4559F373', + defaultMessage: '主机 IP/域名:', + }), + children: {value?.host}, + }, + { + key: 'port', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.FF8162DA', + defaultMessage: '端口', + }), + children: {value?.port}, + }, + ]), + + { + key: 'username', + label: formatMessage({ + id: 'src.component.Task.component.ImportModal.5928CAE8', + defaultMessage: '数据库账号', + }), + children: value?.username || '-', + }, + ]; + + return ( + +

{title}

+ + {items?.map((i) => { + return ( + + {i?.children} + + ); + })} + + + ) : null + } + arrow={false} + > +
{children}
+
+ ); +}; + +export default DatabaseInfoPopover; diff --git a/src/component/Task/component/ImportModal/ImportPreviewTable.tsx b/src/component/Task/component/ImportModal/ImportPreviewTable.tsx index 8dee63f07..774383984 100644 --- a/src/component/Task/component/ImportModal/ImportPreviewTable.tsx +++ b/src/component/Task/component/ImportModal/ImportPreviewTable.tsx @@ -1,15 +1,15 @@ import { formatMessage } from '@/util/intl'; -import React, { useMemo, useState, useEffect } from 'react'; -import { IDatasourceInfo } from '.'; +import React, { useMemo, useState, useEffect, useCallback } from 'react'; +import { IDatasourceInfo, IMPORTABLE_TYPE } from '.'; import { IImportDatabaseView, IImportScheduleTaskView, ScheduleNonImportableType, ScheduleNonImportableTypeMap, } from '@/d.ts/importTask'; -import { Tooltip, Popover, Table, Descriptions, Typography, Empty, Radio } from 'antd'; +import { Tooltip, Table, Typography, Empty, Radio, Checkbox, Flex } from 'antd'; +import { useColumns } from './useColumn'; import type { ColumnsType } from 'antd/es/table'; -import { TaskType } from '@/d.ts'; import { ConnectTypeText } from '@/constant/label'; import Icon from '@ant-design/icons'; import { getDataSourceStyleByConnectType } from '@/common/datasource'; @@ -21,345 +21,279 @@ interface ImportPreviewTableProps { data: IImportScheduleTaskView[]; loading?: boolean; datasourceInfo: IDatasourceInfo; - taskType: TaskType | ScheduleType; -} - -const ImportPreviewTable: React.FC = ({ data, loading, taskType }) => { - const [tableType, setTableType] = useState( - 'importable', - ); - - const DatabaseInfoPopover = ({ - title, - value, - width, - children, - }: { - title: string; - value: IImportDatabaseView; - width: number; - children: React.JSX.Element; - }) => { - const items = [ - { - key: 'datasource', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.A8BC98CA', - defaultMessage: '数据源', - }), - children: value?.name, - }, - { - key: 'databaseName', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.0B1F9DCD', - defaultMessage: '数据库', - }), - children: value?.databaseName, - }, - { - key: 'type', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.263EFA83', - defaultMessage: '类型', - }), - children: ( -
- - - {ConnectTypeText(value?.type)} -
- ), - }, - { - key: 'cloudProvider', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.40440087', - defaultMessage: '云厂商', - }), - children: ( -
- {getCloudProviderName(fromODCPRoviderToProvider[value?.cloudProvider]) || '-'} -
- ), - }, - { - key: 'region', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.F2C95E45', - defaultMessage: '地域', - }), - children:
{value?.region || '-'}
, - }, - { - key: 'host', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.EFE4E0D0', - defaultMessage: '连接信息', - }), - children: ( - - {value?.host}:{value?.port} - - ), - }, - { - key: 'instanceNickName', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.4A911A47', - defaultMessage: '实例名称', - }), - children: value?.instanceNickName || value?.instanceId || '-', - }, - { - key: 'tenantNickName', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.396EF2AD', - defaultMessage: '租户名称', - }), - children: value?.tenantNickName || value?.tenantId || '-', - }, - { - key: 'username', - label: formatMessage({ - id: 'src.component.Task.component.ImportModal.5928CAE8', - defaultMessage: '数据库账号', - }), - children: value?.username || '-', - }, - ]; - - return ( - -

{title}

- - {items?.map((i) => { - return ( - - {i?.children} - - ); - })} - - - ) : null - } - > -
-
{children}
-
-
- ); - }; - - const dlmColumns = [ - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.B4E8467F', - defaultMessage: '源端数据库', - }), - dataIndex: 'databaseView', - key: 'databaseView', - render: (_: IImportDatabaseView) => { - if (!_) return '-'; - return ( - - - {_ ? `${_?.name} / ${_?.databaseName}` : '-'} - - - ); - }, - width: 200, - ellipsis: true, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.76F572C8', - defaultMessage: '目标端数据库', - }), - dataIndex: 'targetDatabaseView', - key: 'targetDatabaseView', - render: (_: IImportDatabaseView) => { - if (!_) return '-'; - return ( - - - {_ ? `${_?.name} / ${_?.databaseName}` : '-'} - - - ); - }, - width: 200, - ellipsis: true, - }, - ]; + taskType: ScheduleType; + projectId: number; + selectedRowKeys: string[]; + setSelectedRowKeys: (selectedRowKeys: string[]) => void; + databaseSelections: Record< + string, + { databaseId: number | null; targetDatabaseId: number | null } + >; - const otherScheduleColumns = [ - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.B5C62BDC', - defaultMessage: '工单描述', - }), - dataIndex: 'description', - key: 'description', - width: 340, - render: (_, record) => { - return _ || '-'; - }, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.1EB75659', - defaultMessage: '数据库', - }), - dataIndex: 'databaseView', - key: 'databaseView', - render: (_: IImportDatabaseView) => { - console.log('databaseView', _); - if (!_) return '-'; - return ( - - - {_ ? `${_?.name} / ${_?.databaseName}` : '-'} - - - ); - }, - width: 200, - ellipsis: true, - }, - ]; - - const columns: ColumnsType = [ - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.5DE578E8', - defaultMessage: '原编号', - }), - dataIndex: 'originId', - key: 'originId', - width: 100, - }, - { - title: formatMessage({ - id: 'src.component.Task.component.ImportModal.2043ADA9', - defaultMessage: '原项目', - }), - dataIndex: 'originProjectName', - key: 'originProjectName', - width: 100, - render: (_) => { - return _ || '-'; - }, - }, - // 数据清理/归档有源端目标端, 分区计划和sql计划只有数据库 - ...([ScheduleType.DATA_ARCHIVE, ScheduleType.DATA_DELETE]?.includes(taskType as ScheduleType) - ? dlmColumns - : []), - ...([ScheduleType.SQL_PLAN, ScheduleType.PARTITION_PLAN]?.includes(taskType as ScheduleType) - ? otherScheduleColumns - : []), - ]?.filter(Boolean); + setDatabaseSelections: React.Dispatch< + React.SetStateAction< + Record + > + >; +} +const ImportPreviewTable: React.FC = ({ + loading, + taskType, + projectId, + data, + selectedRowKeys, + setSelectedRowKeys, + databaseSelections, + setDatabaseSelections, +}) => { + const [showOnlyImportable, setShowOnlyImportable] = useState(false); const groupedData = useMemo(() => { return data.reduce( ( - acc: Record, - + acc: Record, item, ) => { - const groupKey = item.importable ? 'importable' : item.nonImportableType; + let groupKey: ScheduleNonImportableType | 'TO_BE_IMPORTED'; + + if (item.importable) { + groupKey = 'TO_BE_IMPORTED'; + } else { + groupKey = item.nonImportableType; + } + acc[groupKey] = acc[groupKey] || []; acc[groupKey].push(item); return acc; }, - {} as Record, + {} as Record, ); }, [data]); - // 当数据变化时,如果当前tableType不存在则重置为'importable' + const [tableType, setTableType] = useState( + groupedData?.['TO_BE_IMPORTED']?.length > 0 + ? 'TO_BE_IMPORTED' + : Object.values(ScheduleNonImportableType).find((key) => groupedData?.[key]?.length > 0) || + 'TO_BE_IMPORTED', + ); + // 检查一个工单是否已经选择了所需的数据库 + const hasSelectedAllDatabases = useCallback( + (item: IImportScheduleTaskView) => { + const hasSourceDatabase = + item.databaseView?.matchedDatabaseId || databaseSelections[item.originId]?.databaseId; + const hasTargetDatabase = + item.targetDatabaseView?.matchedDatabaseId || + databaseSelections[item.originId]?.targetDatabaseId || + !item?.targetDatabaseView; + + // 如果是数据清理/归档任务,需要检查源端和目标端 + if ([ScheduleType.DATA_ARCHIVE, ScheduleType.DATA_DELETE].includes(taskType)) { + return hasSourceDatabase && hasTargetDatabase; + } + // 其他任务类型只需要检查源端 + return hasSourceDatabase; + }, + [databaseSelections, taskType], + ); + + // 判断工单是否应该被选中 + const shouldBeSelected = useCallback( + (item: IImportScheduleTaskView) => { + if (item.importable && hasSelectedAllDatabases(item)) return true; + return false; + }, + [hasSelectedAllDatabases], + ); + + // 更新选中状态 + const updateSelectedRowKeys = useCallback(() => { + if (!data?.length) return; + + const selectedIds = data.filter((item) => shouldBeSelected(item)).map((item) => item.originId); + + setSelectedRowKeys(selectedIds); + }, [data, shouldBeSelected, setSelectedRowKeys]); + + // 初始化和数据变化时更新选中状态 useEffect(() => { - if (data?.length > 0 && groupedData[tableType] === undefined) { - setTableType('importable'); - } - }, [data]); + updateSelectedRowKeys(); + }, [data, updateSelectedRowKeys]); + + // 数据库选择变化时更新选中状态 + useEffect(() => { + updateSelectedRowKeys(); + }, [databaseSelections, updateSelectedRowKeys]); + + const handleDatabaseChange = useCallback( + (originId: string, type: 'databaseId' | 'targetDatabaseId', databaseId: number) => { + setDatabaseSelections((prev) => ({ + ...prev, + [originId]: { + databaseId: type === 'databaseId' ? databaseId : prev[originId]?.databaseId ?? null, + targetDatabaseId: + type === 'targetDatabaseId' ? databaseId : prev[originId]?.targetDatabaseId ?? null, + }, + })); + }, + [], + ); + + const { importableColumns, typeNotMatchColumns, alreadyExistColumns } = useColumns( + taskType, + projectId, + handleDatabaseChange, + ); + + const handleShowOnlyImportableChange = useCallback( + (checked: boolean) => { + setShowOnlyImportable(checked); + if (checked) { + updateSelectedRowKeys(); + } + }, + [updateSelectedRowKeys], + ); + + const getFilteredData = useCallback( + (data: IImportScheduleTaskView[]) => { + if (!showOnlyImportable) { + return data; + } + return data.filter(shouldBeSelected); + }, + [showOnlyImportable, shouldBeSelected], + ); const tableRender = () => { - if (tableType === 'importable' && !groupedData['importable']) { - return ( - - ); - } return ( <> -
+
+ {!groupedData['TO_BE_IMPORTED'] ? ( + + ) : ( +
{ + setSelectedRowKeys(selectedRowKeys as string[]); + }, + getCheckboxProps: (record) => ({ + disabled: !hasSelectedAllDatabases(record), + }), + }} + /> + )} + +
+
+ +
+
+ ); }; + const tablePrefixRender = (type: ScheduleNonImportableType | 'TO_BE_IMPORTED') => { + const map = { + TO_BE_IMPORTED: ( + + {formatMessage({ + id: 'src.component.Task.component.ImportModal.C264F2F5', + defaultMessage: + '勾选需要导入的工单,导入后将重新启用。导入前请检查涉及的新旧数据库对象是否一致,否则导入或执行时可能出现失败。', + })} + + handleShowOnlyImportableChange(e.target.checked)} + > + {formatMessage({ + id: 'src.component.Task.component.ImportModal.3137AA28', + defaultMessage: '仅显示已选择数据库的工单', + })} + + + ), + + [ScheduleNonImportableType.IMPORTED]: ( +
+ {formatMessage({ + id: 'src.component.Task.component.ImportModal.F8B503DB', + defaultMessage: '以下工单已导入,无需重复操作。', + })} +
+ ), + + [ScheduleNonImportableType.TYPE_NOT_MATCH]: ( +
+ {formatMessage({ + id: 'src.component.Task.component.ImportModal.49FDDB00', + defaultMessage: '以下工单类型不匹配、无法导入,建议选择对应工单类型重新导入。', + })} +
+ ), + }; + return map[type]; + }; + return ( <> - {groupedData['importable']?.length !== data?.length ? ( - setTableType(e.target.value)} - style={{ marginBottom: 16 }} - > - + setTableType(e.target.value)} + style={{ marginBottom: 16 }} + > + {groupedData['TO_BE_IMPORTED']?.length > 0 && ( + {formatMessage({ - id: 'src.component.Task.component.ImportModal.E8DE787E', - defaultMessage: '可导入', + id: 'src.component.Task.component.ImportModal.F42820DF', + defaultMessage: '待导入', })} - {groupedData['importable']?.length || 0} + {groupedData['TO_BE_IMPORTED']?.length || 0} - {Object.keys(ScheduleNonImportableType)?.map((key) => { + )} + + {Object.keys(ScheduleNonImportableType) + ?.filter((key) => groupedData[key as ScheduleNonImportableType]?.length > 0) + ?.map((key) => { return ( {ScheduleNonImportableTypeMap[key as ScheduleNonImportableType]}{' '} @@ -373,8 +307,8 @@ const ImportPreviewTable: React.FC = ({ data, loading, ); })} - - ) : null} + + {tablePrefixRender(tableType)} {tableRender()} ); diff --git a/src/component/Task/component/ImportModal/index.less b/src/component/Task/component/ImportModal/index.less index 76b87d39a..1f1744501 100644 --- a/src/component/Task/component/ImportModal/index.less +++ b/src/component/Task/component/ImportModal/index.less @@ -9,3 +9,28 @@ } } } + +.checkboxError { + color: var(--text-color-error); + :global(.ant-checkbox-inner) { + border-color: var(--text-color-error); + color: var(--text-color-error); + } +} + +.changeDatabaseInput { + :global(.ant-form-item) { + :global(.ant-select) { + :global(.ant-select-selector) { + background-color: var(--table-edit-color); + } + } + } +} + +.databasePopoverName { + cursor: pointer; + &:hover { + color: var(--text-color-link); + } +} diff --git a/src/component/Task/component/ImportModal/index.tsx b/src/component/Task/component/ImportModal/index.tsx index b0d456565..df43f771f 100644 --- a/src/component/Task/component/ImportModal/index.tsx +++ b/src/component/Task/component/ImportModal/index.tsx @@ -5,7 +5,19 @@ import { IImportScheduleTaskView, IScheduleTaskImportRequest, } from '@/d.ts/importTask'; -import { Alert, Button, Form, Input, Modal, Select, Space, Spin, Typography } from 'antd'; +import { + Alert, + Button, + Checkbox, + Flex, + Form, + Input, + Modal, + Select, + Space, + Spin, + Typography, +} from 'antd'; import { CheckCircleFilled, CloseCircleFilled, @@ -27,7 +39,11 @@ import CreateProjectDrawer from '@/page/Project/Project/CreateProject/Drawer'; import NewDatasourceButton from '@/page/Datasource/Datasource/NewDatasourceDrawer/NewButton'; import { listProjects } from '@/common/network/project'; import { TaskTypeMap } from '../TaskTable/const'; +import styles from './index.less'; +import { ScheduleType } from '@/d.ts/schedule'; +import { ScheduleTextMap } from '@/constant/schedule'; +export const IMPORTABLE_TYPE = 'IMPORTABLE_TYPE'; interface IImportModalProps { open: boolean; onCancel: () => void; @@ -36,7 +52,7 @@ interface IImportModalProps { previewData: IImportScheduleTaskView[], projectId?: string, ) => void; - taskType: TaskType; + taskType: ScheduleType; } export interface IDatasourceInfo { @@ -47,6 +63,7 @@ export interface IDatasourceInfo { } const ImportModal: React.FC = ({ open, onCancel, onOk, taskType }) => { const [form] = Form.useForm(); + const [isConfirm, setIsConfirm] = useState(false); const [scheduleTaskImportRequest, setScheduleTaskImportRequest] = useState(); const [previewData, setPreviewData] = useState([]); @@ -58,6 +75,11 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy matchedList: [], createdList: [], }); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [databaseSelections, setDatabaseSelections] = useState< + Record + >({}); + const [notConfirmButSubmit, setNotConfirmButSubmit] = useState(false); const [uploadStatus, setUploadStatus] = useState('default'); const [addProjectDropVisible, setAddProjectDropVisible] = useState(false); const { data: projects, run: loadProjects } = useRequest(listProjects, { @@ -92,9 +114,6 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy setLoading(false); setScheduleTaskImportRequest({ ...params, - importableExportRowId: (previewResult as IImportScheduleTaskView[]) - ?.map((i) => (i.importable ? i?.exportRowId : null)) - ?.filter(Boolean), }); setPreviewData((previewResult as IImportScheduleTaskView[]) || []); setDatasourceInfo( @@ -182,6 +201,9 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy e?.stopPropagation(); setUploadStatus('default'); form.setFieldValue('importFile', []); + setNotConfirmButSubmit(false); + setDatabaseSelections({}); + setSelectedRowKeys([]); }; const uploadInfoMap = useMemo(() => { @@ -255,6 +277,7 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy createdList: [], }); setPreviewData([]); + setIsConfirm(false); }; useEffect(() => { @@ -271,7 +294,7 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy id: 'src.component.Task.component.ImportModal.1825A3A5', defaultMessage: '导入{TaskTypeMapTaskType}', }, - { TaskTypeMapTaskType: TaskTypeMap[taskType] }, + { TaskTypeMapTaskType: ScheduleTextMap[taskType] }, )} destroyOnClose open={open} @@ -298,33 +321,76 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy ) : ( - - - - + + + + + + ) } width={step === 'upload' ? 520 : 960} @@ -334,11 +400,30 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy + {formatMessage({ + id: 'src.component.Task.component.ImportModal.A28A2A00', + defaultMessage: + '仅支持导入由 阿里云 OceanBase 数据研发 或 ODC\n 导出的配置文件;在导入之前,请先将添加相关数据源、 井指定对应的项目。', + })} + + {}}> + + + + } />
@@ -422,89 +507,72 @@ const ImportModal: React.FC = ({ open, onCancel, onOk, taskTy })} /> - - - {formatMessage({ - id: 'src.component.Task.component.ImportModal.B3686BE9', - defaultMessage: '请确认相关数据源已加至项目', - })} - - {}}> - - - - } - > - + (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) + } + open={addProjectDropVisible} + onDropdownVisibleChange={setAddProjectDropVisible} + dropdownRender={(menu) => ( + <> + {menu} + { + setAddProjectDropVisible(false)}> + + {formatMessage({ + id: 'src.component.Task.component.ImportModal.68180A60', + defaultMessage: '新建项目', + })} + + } + buttonType="link" + onCreate={() => loadProjects(null, 1, 9999)} + /> + } + + )} + /> + + )} -
+ {step === 'preview' ? ( -
+ ) : null} ); diff --git a/src/component/Task/component/ImportModal/useColumn.tsx b/src/component/Task/component/ImportModal/useColumn.tsx new file mode 100644 index 000000000..9bc73f417 --- /dev/null +++ b/src/component/Task/component/ImportModal/useColumn.tsx @@ -0,0 +1,296 @@ +import { formatMessage } from '@/util/intl'; +import { IImportDatabaseView, IImportScheduleTaskView } from '@/d.ts/importTask'; +import { Tooltip, Typography, Flex } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import { TaskStatus } from '@/d.ts'; +import Icon, { InfoCircleOutlined } from '@ant-design/icons'; +import StatusLabel from '../Status'; +import DatabaseChangeItem from './DatabaseChangeItem'; +import DatabaseInfoPopover from './DatabaseInfoPopover'; +import { ReactComponent as SourceDatabase } from '@/svgr/source_database.svg'; +import { ReactComponent as TargetDatabase } from '@/svgr/target_database.svg'; +import styles from './index.less'; +import { TaskTypeMap } from '../TaskTable/const'; +import { ScheduleStatus, ScheduleType } from '@/d.ts/schedule'; +import ScheduleStatusLabel from '@/component/Schedule/components/ScheduleStatusLabel'; + +export const useColumns = ( + taskType: ScheduleType, + projectId: number, + handleDatabaseChange: ( + originId: string, + type: 'databaseId' | 'targetDatabaseId', + databaseId: number, + ) => void, +) => { + const baseInfoColumns = [ + { + title: formatMessage({ + id: 'src.component.Task.component.ImportModal.7C3EB72C', + defaultMessage: '工单', + }), + dataIndex: 'description', + key: 'description', + width: 300, + ellipsis: true, + render: (_, record) => { + return ( + + {_ || '-'} + + #{record?.originId} + · + + {record?.originProjectName} + + + + ); + }, + }, + ]; + + const originDatabaseColumns = [ + { + title: formatMessage({ + id: 'src.component.Task.component.ImportModal.DCD1BA1A', + defaultMessage: '原数据库', + }), + dataIndex: 'databaseView', + key: 'databaseView', + width: 140, + render: (_: IImportDatabaseView) => { + if (!_) return '-'; + return ( + + + + {_ ? `${_?.databaseName}` : '-'} + + + {formatMessage({ + id: 'src.component.Task.component.ImportModal.A8B5EF34', + defaultMessage: '数据源:', + })} + {_?.name} + + + + ); + }, + }, + ]; + + const originDatabaseWithTargetColumns = [ + { + title: formatMessage({ + id: 'src.component.Task.component.ImportModal.4C0466BB', + defaultMessage: '原数据库', + }), + dataIndex: 'databaseId', + key: 'databaseId', + width: 150, + render: (databaseId: number, record: IImportScheduleTaskView) => { + return ( + + + + + + {record.databaseView?.databaseName || '-'} + + + + + + + + {record.targetDatabaseView?.databaseName || '-'} + + + + + ); + }, + }, + ]; + + const originStatusColumns = [ + { + title: formatMessage({ + id: 'src.component.Task.component.ImportModal.55E87B63', + defaultMessage: '原状态', + }), + dataIndex: 'originStatus', + key: 'originStatus', + width: 120, + render: (status: ScheduleStatus, record) => ( + + + {status !== ScheduleStatus.ENABLED && ( + + + + )} + + ), + }, + ]; + + const originDatabaseTypeColumns = [ + { + title: formatMessage({ + id: 'src.component.Task.component.ImportModal.37A63CCA', + defaultMessage: '类型', + }), + dataIndex: 'taskType', + key: 'taskType', + width: 120, + render: (status, record) => { + return TaskTypeMap[record.type]; + }, + }, + ]; + + const dlmColumns = [ + ...originDatabaseWithTargetColumns, + { + title: formatMessage({ + id: 'src.component.Task.component.ImportModal.D22149A7', + defaultMessage: '新源端', + }), + dataIndex: 'databaseView', + key: 'databaseView', + width: 200, + render: (_: IImportDatabaseView, record: IImportScheduleTaskView) => { + if (!_) return '-'; + return ( + + handleDatabaseChange(record.originId, 'databaseId', databaseId) + } + /> + ); + }, + }, + { + title: formatMessage({ + id: 'src.component.Task.component.ImportModal.6992AD58', + defaultMessage: '新目标端', + }), + dataIndex: 'targetDatabaseView', + key: 'targetDatabaseView', + width: 200, + render: (_: IImportDatabaseView, record: IImportScheduleTaskView) => { + if (!_) return '-'; + return ( + + handleDatabaseChange(record.originId, 'targetDatabaseId', databaseId) + } + /> + ); + }, + }, + ]; + + const otherScheduleColumns = [ + ...originDatabaseColumns, + { + title: formatMessage({ + id: 'src.component.Task.component.ImportModal.01BA659E', + defaultMessage: '新数据库', + }), + dataIndex: 'databaseView', + key: 'databaseView', + width: 200, + render: (_: IImportDatabaseView, record: IImportScheduleTaskView) => { + return ( + + handleDatabaseChange(record.originId, 'databaseId', databaseId) + } + /> + ); + }, + }, + ]; + + const typeNotMatchColumns = [ + ...baseInfoColumns, + ...originDatabaseColumns, + ...originDatabaseTypeColumns, + ]; + + const alreadyExistColumns = [ + ...baseInfoColumns, + ...([ScheduleType.DATA_ARCHIVE, ScheduleType.DATA_DELETE]?.includes(taskType) + ? originDatabaseWithTargetColumns + : []), + ...([ScheduleType.SQL_PLAN, ScheduleType.PARTITION_PLAN]?.includes(taskType) + ? originDatabaseColumns + : []), + ]; + + const importableColumns: ColumnsType = [ + ...baseInfoColumns, + ...originStatusColumns, + // 数据清理/归档有源端目标端, 分区计划和sql计划只有数据库 + ...([ScheduleType.DATA_ARCHIVE, ScheduleType.DATA_DELETE]?.includes(taskType) + ? dlmColumns + : []), + ...([ScheduleType.SQL_PLAN, ScheduleType.PARTITION_PLAN]?.includes(taskType) + ? otherScheduleColumns + : []), + ]?.filter(Boolean); + + return { + importableColumns, + typeNotMatchColumns, + alreadyExistColumns, + }; +}; diff --git a/src/component/Task/component/ImportModal/useImport.tsx b/src/component/Task/component/ImportModal/useImport.tsx index 8e8da0a66..83671527f 100644 --- a/src/component/Task/component/ImportModal/useImport.tsx +++ b/src/component/Task/component/ImportModal/useImport.tsx @@ -16,6 +16,7 @@ import { useDebounceFn } from 'ahooks'; import React, { useState } from 'react'; import { AsyncTaskType } from '@/d.ts/migrateTask'; import { history } from '@umijs/max'; +import login from '@/store/login'; const downloadLogFromString = (str: string) => { const blob = new Blob([str], { type: 'text/plain' }); @@ -62,20 +63,44 @@ export const useImport = ( downloadLogFromString(res); }; if (result?.every((i) => i.success)) { + const importedCount = result?.filter((i) => i?.remark === 'Have been imported.')?.length; + const importedDescription = importedCount + ? formatMessage( + { + id: 'src.component.Task.component.ImportModal.A6892359', + defaultMessage: '(包含 {importedCount} 个已导入的任务)', + }, + { importedCount }, + ) + : ''; + const privateSpaceDescription = formatMessage( + { + id: 'src.component.Task.component.ImportModal.AF106FF6', + defaultMessage: '{resultLength} 个作业导入成功{importedDescription}。', + }, + { resultLength: result?.length, importedDescription }, + ); notification.success({ message: formatMessage({ id: 'src.component.Task.component.ImportModal.997B6AC7', defaultMessage: '导入定时任务已完成', }), - description: ( + description: login.isPrivateSpace() ? ( + privateSpaceDescription + ) : ( {formatMessage( { - id: 'src.component.Task.component.ImportModal.E256F212', - defaultMessage: '{resultLength} 个作业导入成功。 建议手动为任务', + id: 'src.component.Task.component.ImportModal.97A1A7A3', + defaultMessage: '{resultLength} 个作业导入成功{importedDescription}。', }, - { resultLength: result?.length }, + { resultLength: result?.length, importedDescription }, )} + {formatMessage({ + id: 'src.component.Task.component.ImportModal.1B653B27', + defaultMessage: '建议手动为任务', + })} + goNotification(projectId)}> {formatMessage({ id: 'src.component.Task.component.ImportModal.8AD83BC8', @@ -94,19 +119,59 @@ export const useImport = ( } else { const successCount = result?.filter((i) => i?.success)?.length; const failedCount = result?.filter((i) => !i?.success)?.length; + const importedCount = result?.filter( + (i) => i?.success && i?.remark === 'Have been imported.', + )?.length; + const importedDescription = importedCount + ? formatMessage( + { + id: 'src.component.Task.component.ImportModal.92D087FB', + defaultMessage: '(包含 {importedCount} 个已导入的任务)', + }, + { importedCount }, + ) + : ''; + const privateSpaceDescription = ( + + {formatMessage( + { + id: 'src.component.Task.component.ImportModal.94BFD01C', + defaultMessage: + '{successCount} 个作业导入成功{importedDescription}, {failedCount} 个作业导入失败。可', + }, + { successCount, importedDescription, failedCount }, + )} + + {formatMessage({ + id: 'src.component.Task.component.ImportModal.A23B9738', + defaultMessage: '下载日志', + })} + + {formatMessage({ + id: 'src.component.Task.component.ImportModal.997B48CD', + defaultMessage: '查看。', + })} + + ); + notification.warning({ message: formatMessage({ - id: 'src.component.Task.component.ImportModal.E12ACFFB', - defaultMessage: '导出定时任务已完成', + id: 'src.component.Task.component.ImportModal.997B6AC7', + defaultMessage: '导入定时任务已完成', }), - description: ( + description: login.isPrivateSpace() ? ( + privateSpaceDescription + ) : ( {formatMessage( { - id: 'src.component.Task.component.ImportModal.47DA5967', - defaultMessage: '{successCount} 个作业导入成功,', + id: 'src.component.Task.component.ImportModal.AE88E265', + defaultMessage: '{successCount} 个作业导入成功{importedDescription}, ', }, - { successCount }, + { successCount, importedDescription }, )} {formatMessage( { @@ -128,7 +193,6 @@ export const useImport = ( id: 'src.component.Task.component.ImportModal.60663407', defaultMessage: ',保证任务异常能够被及时发现。如需了解导出详情,可', })} - = (props) => { if (isBatch || isSingleGenerateCount) { return ( - {isSingleGenerateCount &&
{isSingleGenerateCountMessage}
} - {isBatch &&
{isBatchMessage}
} - + isSingleGenerateCount ? ( + <> +
{isSingleGenerateCountMessage}
+
{isBatchMessage}
+ + ) : ( + isBatchMessage + ) } /*批量设置将覆盖原有的策略,是否确定设置?*/ onConfirm={handleOk} okText={formatMessage({ @@ -396,7 +404,15 @@ const ConfigDrawer: React.FC = (props) => { defaultMessage: '返回', })} /*返回*/ > - {renderConfirmButton()} +
); } diff --git a/src/component/Task/component/TaskTable/index.tsx b/src/component/Task/component/TaskTable/index.tsx index 0b97d27dc..6efac1d81 100644 --- a/src/component/Task/component/TaskTable/index.tsx +++ b/src/component/Task/component/TaskTable/index.tsx @@ -8,6 +8,8 @@ import type { PageStore } from '@/store/page'; import type { TaskStore } from '@/store/task'; import { useLoop } from '@/util/hooks/useLoop'; import { formatMessage } from '@/util/intl'; +import { getLocalFormatDateTime } from '@/util/utils'; +import { flatten } from 'lodash'; import Icon, { DownOutlined, SearchOutlined } from '@ant-design/icons'; import { Button, DatePicker, Tooltip, Popover, Space, Typography, Dropdown } from 'antd'; import { inject, observer } from 'mobx-react'; @@ -88,9 +90,6 @@ const TaskTable: React.FC = (props) => { const isAll = TaskPageType.ALL === taskTabType; const [hoverInNewTaskMenuBtn, setHoverInNewTaskMenuBtn] = useState(false); const [hoverInNewTaskMenu, setHoverInNewTaskMenu] = useState(false); - const [importModalVisible, setImportModalVisible] = useState(false); - const [importProjectId, setImportProjectId] = useState(); - const { isSubmitImport, debounceSubmit } = useImport(props.onReloadList, importProjectId); const { runAction } = useUrlAction(); const { loop: loadData, destory } = useLoop((count) => { @@ -302,7 +301,7 @@ const TaskTable: React.FC = (props) => { ); - }, [isAll, taskTabType, isSubmitImport]); + }, [isAll, taskTabType]); const batchOperation = () => { if (projectArchived) return; @@ -409,16 +408,6 @@ const TaskTable: React.FC = (props) => { showSelectedInfoBar={false} rowSelecter={[TaskPageType.ALL]?.includes(taskTabType) ? null : rowSelection} /> - setImportModalVisible(false)} - onOk={(scheduleTaskImportRequest, previewData, projectId) => { - setImportModalVisible(false); - setImportProjectId(projectId); - debounceSubmit(scheduleTaskImportRequest, previewData); - }} - /> ); }; diff --git a/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx b/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx index 12f0097ed..d867d2e55 100644 --- a/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx +++ b/src/component/Task/modals/ApplyDatabasePermission/DetailContent/index.tsx @@ -108,7 +108,7 @@ const TaskContent: React.FC = (props) => { @@ -157,6 +157,11 @@ const TaskContent: React.FC = (props) => { direction="column" /> + = (props) => { diff --git a/src/component/Task/modals/ApplyPermission/DetailContent/index.tsx b/src/component/Task/modals/ApplyPermission/DetailContent/index.tsx index 1b38a975b..a623c091e 100644 --- a/src/component/Task/modals/ApplyPermission/DetailContent/index.tsx +++ b/src/component/Task/modals/ApplyPermission/DetailContent/index.tsx @@ -78,7 +78,7 @@ const ApplyPermissionTaskContent: React.FC = (props) => { diff --git a/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx b/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx index d07058908..91c7c80b4 100644 --- a/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx +++ b/src/component/Task/modals/ApplyTablePermission/DetailContent/index.tsx @@ -22,9 +22,9 @@ import type { IApplyTablePermissionTaskParams, TaskDetail } from '@/d.ts'; import { getFormatDateTime } from '@/util/utils'; import { Descriptions, Divider, Space } from 'antd'; import { useMemo } from 'react'; -import { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; import { permissionOptionsMap } from '../'; import { getExpireTimeLabel } from '@/component/Task/helper'; +import { ODCRiskLevelLabel } from '@/component/RiskLevelLabel'; import EllipsisText from '@/component/EllipsisText'; const getConnectionColumns = () => { @@ -122,7 +122,7 @@ const TaskContent: React.FC = (props) => { @@ -157,7 +157,7 @@ const TaskContent: React.FC = (props) => { @@ -189,7 +189,7 @@ const TaskContent: React.FC = (props) => { diff --git a/src/component/Task/modals/DataMockerTask/CreateModal/form.tsx b/src/component/Task/modals/DataMockerTask/CreateModal/form.tsx index 037a30617..2464cc240 100644 --- a/src/component/Task/modals/DataMockerTask/CreateModal/form.tsx +++ b/src/component/Task/modals/DataMockerTask/CreateModal/form.tsx @@ -212,7 +212,7 @@ const DataMockerForm: React.FC = inject('settingStore')( form.resetFields(['tableName', 'columns']); }} projectId={projectId} - width={'441px'} + width={441} type={TaskType.DATAMOCK} /> diff --git a/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx b/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx index e55e7c905..4aaac729b 100644 --- a/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx +++ b/src/component/Task/modals/DataMockerTask/CreateModal/index.tsx @@ -182,7 +182,7 @@ const CreateModal: React.FC = inject('modalStore')( = (props) => { diff --git a/src/component/Task/modals/MutipleAsyncTask/DetailContent/index.tsx b/src/component/Task/modals/MutipleAsyncTask/DetailContent/index.tsx index 85b717c4b..24ca90a77 100644 --- a/src/component/Task/modals/MutipleAsyncTask/DetailContent/index.tsx +++ b/src/component/Task/modals/MutipleAsyncTask/DetailContent/index.tsx @@ -54,7 +54,7 @@ const MutipleAsyncTaskContent: React.FC = const taskExecStrategyMap = getTaskExecStrategyMap(task?.type); return ( <> - + {task?.id} {TaskTypeMap?.[task?.type]} = {task?.description || '-'} - + = ({ projectId, modalStore }) => = ({ projectId, modalStore }) => /> { async function checkTableRowCounts(oldTables: string[]): Promise { log.info('Checking row counts for each table...'); try { - const sql = oldTables.map((table) => `SELECT COUNT(*) as c FROM ${table}`).join(' union all '); - const result = await executeSqlInOldH2(sql); - const newResult = await executeSqlInNewH2(sql); - const oldCounts = result.split('\n').filter(Boolean).slice(1, -1); - const newCounts = newResult.split('\n').filter(Boolean).slice(1, -1); - log.info('sql: ', sql, 'oldCounts: ', oldCounts, 'newCounts: ', newCounts); - if (oldCounts.length !== newCounts.length) { - log.error('Row count mismatch for all tables! OLD: ', oldCounts, 'NEW: ', newCounts); - return false; + /** + * 由于命令行的长度限制,需要把这条语句拆分成多条来执行 + * 按照 windows 8191的限制,每条语句按照100个字符来计算,每次执行的语句不能超过81张表,保险一点,按照50张表来处理 + */ + const maxTableCount = 50; + for (let i = 0; i < oldTables.length; i += maxTableCount) { + const sql = oldTables + .slice(i, i + maxTableCount) + .map((table) => `SELECT COUNT(*) as c FROM ${table}`) + .join(' union all '); + const result = await executeSqlInOldH2(sql); + const newResult = await executeSqlInNewH2(sql); + const oldCounts = result.split('\n').filter(Boolean).slice(1, -1); + const newCounts = newResult.split('\n').filter(Boolean).slice(1, -1); + log.info('sql: ', sql, 'oldCounts: ', oldCounts, 'newCounts: ', newCounts); + if (oldCounts.length !== newCounts.length) { + log.error('Row count mismatch for all tables! OLD: ', oldCounts, 'NEW: ', newCounts); + return false; + } } log.info('Row counts match for all tables!'); @@ -219,7 +229,7 @@ async function exportSql(): Promise { try { const { stdout, stderr } = await execAsync( - `"${JAVA_PATH}" -cp "${OLD_H2_JAR_PATH}" org.h2.tools.Script -url "${OLD_H2_URL}" -user "${DB_USERNAME}" -password "${DB_PASSWORD}" -script "${TMP_EXPORT_SQL_PATH}" -options 'DROP'`, + `"${JAVA_PATH}" -cp "${OLD_H2_JAR_PATH}" org.h2.tools.Script -url "${OLD_H2_URL}" -user "${DB_USERNAME}" -password "${DB_PASSWORD}" -script "${TMP_EXPORT_SQL_PATH}" -options "DROP"`, ); if (!fs.existsSync(TMP_EXPORT_SQL_PATH)) { log.error('Failed Export Sql\n', 'stdout:', stdout, 'error:', stderr); @@ -339,14 +349,17 @@ async function checkH2Connection(): Promise { try { return !!(await executeSqlInNewH2('SELECT * FROM dual')); } catch (error) { + log.error('H2 connection check failed...', error); return await showRecoverDialog(); } } async function moveOldFileToBackup(): Promise { + log.info('Moving old file to backup...'); if (fs.existsSync(OLD_H2_PATH)) { await fsPromises.rename(OLD_H2_PATH, OLD_H2_BACKUP_PATH); } + log.info('Move old file to backup success'); } /** @@ -358,10 +371,17 @@ export async function runH2Migration(): Promise { log.error('Migration failed...'); return false; } + log.info('Checking h2 connection...'); if (!(await checkH2Connection())) { + log.error('H2 connection check failed...'); return false; } + log.info('H2 connection check success'); + log.info('Moving old file to backup...'); await moveOldFileToBackup(); - clear(); + log.info('Moving old file to backup success'); + log.info('Clearing temporary files...'); + await clear(); + log.info('Clearing temporary files success'); return true; } diff --git a/src/page/Auth/User/index.tsx b/src/page/Auth/User/index.tsx index 6d4b81d98..8fbb9bad8 100644 --- a/src/page/Auth/User/index.tsx +++ b/src/page/Auth/User/index.tsx @@ -131,6 +131,14 @@ class UserPage extends React.PureComponent { value: id, }; }), + onFilter: (value, record) => { + // 如果过滤值为0,表示选择"空"选项,匹配没有角色的用户 + if (value === 0) { + return !record?.roles?.length; + } + // 检查用户是否包含选中的角色 + return record?.roles?.some((role) => role.id === value); + }, render: (roles) => { return ; }, diff --git a/src/page/Console/components/RecentlyDatabase/index.less b/src/page/Console/components/RecentlyDatabase/index.less index 2940b387c..bba49e8f2 100644 --- a/src/page/Console/components/RecentlyDatabase/index.less +++ b/src/page/Console/components/RecentlyDatabase/index.less @@ -2,14 +2,15 @@ margin-top: 16px; overflow: auto; cursor: default; - .action, - .disabledAction { + .action { cursor: pointer; font-size: 12px; color: var(--text-color-link); line-height: 20px; } .disabledAction { + font-size: 12px; + line-height: 20px; filter: grayscale(1); color: #00000040; } diff --git a/src/page/Console/components/RecentlyDatabase/index.tsx b/src/page/Console/components/RecentlyDatabase/index.tsx index b9fb23644..3dbdc1912 100644 --- a/src/page/Console/components/RecentlyDatabase/index.tsx +++ b/src/page/Console/components/RecentlyDatabase/index.tsx @@ -26,11 +26,15 @@ import { getRecentlyDatabaseOperation } from './help'; import LogicDatabaseAsyncTask from '@/component/Task/modals/LogicDatabaseAsyncTask'; import LogicIcon from '@/component/logicIcon'; import HelpDoc from '@/component/helpDoc'; +import { IForbiddenSQLConsoleDataSourceType } from '@/d.ts/datasource'; interface IProps { modalStore?: ModalStore; } - +enum ETootipType { + DATABASE = 'database', + PROJECT = 'project', +} const RecentlyDatabase: React.FC = ({ modalStore }) => { const { data: databaseList, @@ -62,7 +66,7 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { const renderTooltipContent = ({ type, record }) => { switch (type) { - case 'project': + case ETootipType.PROJECT: return (
{formatMessage( @@ -88,7 +92,7 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => {
); - case 'database': + case ETootipType.DATABASE: return (
{formatMessage({ @@ -126,20 +130,39 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { ellipsis: true, width: columnWidth[index], render: (value, record) => { - const hasProjectAuth = record?.project?.currentUserResourceRoles?.length > 0; - const hasDBAuth = !!record?.authorizedPermissionTypes?.length; - const actionStyle = hasProjectAuth ? styles.action : styles.disabledAction; - const normalStyle = hasProjectAuth ? '' : styles.disabledAction; + const needToApplyProjectAuth = !record?.project?.currentUserResourceRoles?.length; + const needToApplyDatabaseAuth = !record?.authorizedPermissionTypes?.length; + + const forbiddenAccessSQLConsole = [ + IForbiddenSQLConsoleDataSourceType.OSS, + IForbiddenSQLConsoleDataSourceType.COS, + IForbiddenSQLConsoleDataSourceType.AWS, + IForbiddenSQLConsoleDataSourceType.OBS, + ].includes(record?.dataSource?.type); + + const disabledAllOperations = needToApplyProjectAuth || forbiddenAccessSQLConsole; + + const recordWithActionClassName = disabledAllOperations + ? styles.disabledAction + : styles.action; + + const recordWithoutActionClassName = disabledAllOperations ? styles.disabledAction : ''; + switch (key) { case EDatabaseTableColumnKey.Operation: const operation = getRecentlyDatabaseOperation({ record, project: record?.project }); return (
{operation.map((item, index) => { + if (disabledAllOperations || needToApplyDatabaseAuth) { + item.disable = true; + } return renderTool(item, index); })} @@ -150,67 +173,85 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { const databaseStyle = getDataSourceStyleByConnectType(record?.dataSource?.type); const existed = record?.existed; return ( -
- - { - if (!existed) { - return; - } - gotoSQLWorkspace( - record?.project?.id, - record?.dataSource?.id, - record?.id, - null, - '', - isLogicalDatabase(record), - ); - }} - > - {value} - {!existed && ( - - )} - - +
- -
- ) : ( - - ) - } - /> -
+ > + + { + if (!existed || disabledAllOperations || needToApplyDatabaseAuth) { + return; + } + gotoSQLWorkspace( + record?.project?.id, + record?.dataSource?.id, + record?.id, + null, + '', + isLogicalDatabase(record), + ); + }} + > + {value} + {!existed && ( + + )} + + + } + icon={ + record?.type === 'LOGICAL' ? ( +
+ +
+ ) : ( + + ) + } + /> +
+ ); case EDatabaseTableColumnKey.DataSource: @@ -218,9 +259,9 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { if (!value) { return ( @@ -230,14 +271,18 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { } return ( -
+
@@ -261,8 +306,11 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { case EDatabaseTableColumnKey.Project: return (
{ + if (disabledAllOperations) { + return; + } window.open(`#/project/${value.id}/database`); }} > @@ -301,6 +349,7 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { ) : ( )} + diff --git a/src/page/Console/const.tsx b/src/page/Console/const.tsx index b7c3b1836..13ec5384e 100644 --- a/src/page/Console/const.tsx +++ b/src/page/Console/const.tsx @@ -1,4 +1,9 @@ import { formatMessage } from '@/util/intl'; +import { TaskPageType } from '@/d.ts'; +import { ReactComponent as DownloadSvg } from '@/svgr/download-fill.svg'; +import { ReactComponent as GithubSvg } from '@/svgr/github.svg'; +import { ReactComponent as SendSvg } from '@/svgr/send-fill.svg'; +import Icon from '@ant-design/icons'; import { TaskType } from '@/d.ts'; import { ScheduleType } from '@/d.ts/schedule'; diff --git a/src/page/Console/index.less b/src/page/Console/index.less index 122393d8e..ccaa7a2fa 100644 --- a/src/page/Console/index.less +++ b/src/page/Console/index.less @@ -266,7 +266,6 @@ } .aboutUsContent { display: flex; - align-items: center; justify-content: space-between; } .article { diff --git a/src/page/Console/index.tsx b/src/page/Console/index.tsx index ce44ee559..23352fdce 100644 --- a/src/page/Console/index.tsx +++ b/src/page/Console/index.tsx @@ -4,6 +4,8 @@ import { useMount, useRequest } from 'ahooks'; import dayjs, { Dayjs } from 'dayjs'; import { Card, Col, Divider, Row, Select, Spin, Typography, DatePicker } from 'antd'; import odc from '@/plugins/odc'; +import { getImg } from '@/util/intl'; + import { ReactComponent as DownloadSvg } from '@/svgr/download-fill.svg'; import { ReactComponent as GithubSvg } from '@/svgr/github.svg'; import { ReactComponent as SendSvg } from '@/svgr/send-fill.svg'; diff --git a/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/index.tsx b/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/index.tsx index 7fcfc8490..a555ff5f4 100644 --- a/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/index.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/index.tsx @@ -96,7 +96,12 @@ const DatabaseTree = function () { const group = DatabaseGroupMap?.[item]?.entries()?.next()?.value?.[1]; defaultExpandedKeys.push(getGroupKey(group?.mapId, item)); if ( - [DatabaseGroup.cluster, DatabaseGroup.environment, DatabaseGroup.connectType].includes(item) + [ + DatabaseGroup.cluster, + DatabaseGroup.environment, + DatabaseGroup.connectType, + DatabaseGroup.tenant, + ].includes(item) ) { const secondGroup = group?.secondGroup?.entries()?.next()?.value?.[1]; defaultExpandedKeys.push(getSecondGroupKey(group?.mapId, secondGroup?.mapId, item)); diff --git a/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/useGroupData.ts b/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/useGroupData.ts index 1dbf985e4..298650d96 100644 --- a/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/useGroupData.ts +++ b/src/page/Workspace/SideBar/ResourceTree/DatabaseTree/useGroupData.ts @@ -23,7 +23,7 @@ const useGroupData = (props: IProps) => { const datasourceGruop: Map = new Map(); const clusterGroup: Map = new Map(); const projectGroup: Map = new Map(); - const tenantGroup: Map = new Map(); + const tenantGroup: Map = new Map(); const allDatabases: Map = new Map(); const filteredList = filter ? databaseList?.filter(filter) : databaseList; const allDatasources: IConnection[] = []; @@ -152,19 +152,30 @@ const useGroupData = (props: IProps) => { // 租户分组 { const { mapId, groupName, tip } = getMapIdByDB(db, DatabaseGroup.tenant); - const tenantDatabases: GroupWithDatabases[DatabaseGroup.tenant] = tenantGroup.get( + const tenantDatabases: GroupWithSecondGroup[DatabaseGroup.tenant] = tenantGroup.get( mapId, ) || { groupName, mapId, tip, - databases: [], + secondGroup: new Map(), }; + const { mapId: secondGroupMapId, groupName: secondGroupgroupName } = getMapIdByDB( + db, + DatabaseGroup.dataSource, + ); + const secondGroupDatabase: GroupWithDatabases[DatabaseGroup.dataSource] = + tenantDatabases.secondGroup.get(secondGroupMapId) || { + databases: [], + groupName: secondGroupgroupName, + mapId: secondGroupMapId, + }; if (db.type === 'LOGICAL') { - tenantDatabases.databases.unshift(db); + secondGroupDatabase.databases.unshift(db); } else { - tenantDatabases.databases.push(db); + secondGroupDatabase.databases.push(db); } + tenantDatabases.secondGroup.set(secondGroupMapId, secondGroupDatabase); tenantGroup.set(mapId, tenantDatabases); } }); diff --git a/src/page/Workspace/SideBar/ResourceTree/const.ts b/src/page/Workspace/SideBar/ResourceTree/const.ts index 1ea56c15f..11252590a 100644 --- a/src/page/Workspace/SideBar/ResourceTree/const.ts +++ b/src/page/Workspace/SideBar/ResourceTree/const.ts @@ -107,7 +107,7 @@ const getShouldExpandedGroupKeys = (params: { getGroupKey(mapId, groupMode), getSecondGroupKey(mapId, secondMapId, groupMode), ); - if ([DatabaseGroup.project, DatabaseGroup.dataSource, DatabaseGroup.tenant].includes(groupMode)) { + if ([DatabaseGroup.project, DatabaseGroup.dataSource].includes(groupMode)) { shouldExpandedKeys = shouldExpandedKeys.filter((item) => { if (isString(item)) { return !item.includes(TreeDataSecondGroupKey); @@ -225,7 +225,7 @@ const getShouldExpandedKeysByObject = (params: { break; } } - if ([DatabaseGroup.project, DatabaseGroup.dataSource, DatabaseGroup.tenant].includes(groupMode)) { + if ([DatabaseGroup.project, DatabaseGroup.dataSource].includes(groupMode)) { shouldExpandedKeys = shouldExpandedKeys.filter((item) => { if (isString(item)) { return !item?.includes(TreeDataSecondGroupKey); @@ -414,7 +414,7 @@ const getObjectShouldExpandedKeysByPage = (params: { break; } } - if ([DatabaseGroup.project, DatabaseGroup.dataSource, DatabaseGroup.tenant].includes(groupMode)) { + if ([DatabaseGroup.project, DatabaseGroup.dataSource].includes(groupMode)) { shouldExpandedKeys = shouldExpandedKeys.filter((item) => { if (isString(item)) { return !item?.includes(TreeDataSecondGroupKey); diff --git a/src/page/Workspace/SideBar/ResourceTree/index.tsx b/src/page/Workspace/SideBar/ResourceTree/index.tsx index c56c2f555..90da9ed73 100644 --- a/src/page/Workspace/SideBar/ResourceTree/index.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/index.tsx @@ -263,8 +263,7 @@ const ResourceTree: React.FC = function ({ }); } case DatabaseGroup.project: - case DatabaseGroup.dataSource: - case DatabaseGroup.tenant: { + case DatabaseGroup.dataSource: { return databases.map((groupItem) => { const groupKey = getGroupKey(groupItem.mapId, groupMode); let data, icon; @@ -276,7 +275,6 @@ const ResourceTree: React.FC = function ({ } return { title: groupItem.groupName, - tip: groupItem.tip, key: groupKey, type: GroupNodeToResourceNodeType[groupMode], data: data ?? null, @@ -306,11 +304,13 @@ const ResourceTree: React.FC = function ({ } case DatabaseGroup.cluster: case DatabaseGroup.environment: - case DatabaseGroup.connectType: { + case DatabaseGroup.connectType: + case DatabaseGroup.tenant: { return databases.map((groupItem) => { const groupKey = getGroupKey(groupItem.mapId, groupMode); return { title: groupItem.groupName, + tip: groupItem.tip, key: groupKey, type: GroupNodeToResourceNodeType[groupMode], children: [...groupItem.secondGroup.values()]?.map((sItem) => { diff --git a/src/page/Workspace/components/CreateViewPage/index.tsx b/src/page/Workspace/components/CreateViewPage/index.tsx index 3cb455e7c..cca7f942b 100644 --- a/src/page/Workspace/components/CreateViewPage/index.tsx +++ b/src/page/Workspace/components/CreateViewPage/index.tsx @@ -114,9 +114,14 @@ const CreateViewPage: React.FC = inject( session?.odcDatabase?.name, ); setCreateCheckResults(results); - if (results?.invalid || !results?.executeResult?.length || !results.unauthorizedDBResources) { + if (results?.invalid || !results?.executeResult?.length) { return; } + + if (results?.unauthorizedDBResources && !results?.unauthorizedDBResources?.length) { + return; + } + const { dbObjectName: viewName, track } = results.executeResult[0]; if (!track) { message.success( diff --git a/src/page/Workspace/components/DDLResultSet/index.tsx b/src/page/Workspace/components/DDLResultSet/index.tsx index 59541544f..238f8357c 100644 --- a/src/page/Workspace/components/DDLResultSet/index.tsx +++ b/src/page/Workspace/components/DDLResultSet/index.tsx @@ -158,6 +158,10 @@ interface IProps { * db 查询耗时 */ dbTotalDurationMicroseconds?: number; + /** + * 外部传入的初始limit值,优先级高于session中的queryLimit + */ + initialLimit?: number; onRefresh?: (limit: number) => void; onSubmitRows?: ( newRows, @@ -177,6 +181,7 @@ interface IProps { ) => void; isExternalTable?: boolean; // 是否为外表 } + const DDLResultSet: React.FC = function (props) { const { isTableData, @@ -206,6 +211,7 @@ const DDLResultSet: React.FC = function (props) { withFullLinkTrace = false, withQueryProfile = false, traceEmptyReason = '', + initialLimit, onUpdateEditing, onRefresh, onShowExecuteDetail, @@ -229,7 +235,9 @@ const DDLResultSet: React.FC = function (props) { /** * 数据量限制 */ - const [limit, setLimit] = useState(1000); + const [limit, setLimit] = useState(() => { + return initialLimit ?? session?.params?.queryLimit; + }); /** * 表数据搜索 */ @@ -402,7 +410,7 @@ const DDLResultSet: React.FC = function (props) { gridRef.current?.scrollToRow(0); }, [gridRef]); const handleExport = useCallback(() => { - onExport?.(limit || 1000); + onExport?.(limit); }, [onExport, limit]); const handleEditPropertyInCell = useCallback( (newRows) => { @@ -971,7 +979,7 @@ const DDLResultSet: React.FC = function (props) { key="commit" onConfirm={async () => { await sqlStore.commit(props.pageKey, sessionId, session?.database?.dbName); - onRefresh(limit || 1000); + onRefresh(limit); }} disabled={isInTransaction} > @@ -987,7 +995,7 @@ const DDLResultSet: React.FC = function (props) { key="rollback" onConfirm={async () => { await sqlStore.rollback(props.pageKey, sessionId, session?.database?.dbName); - onRefresh(limit || 1000); + onRefresh(limit); }} isRollback disabled={isInTransaction} @@ -1238,22 +1246,32 @@ const DDLResultSet: React.FC = function (props) { { if (limit == '' || isNil(limit)) { - setLimit(0); + setLimit(1); } }} - onChange={(limit) => setLimit(limit || 0)} + onChange={(limit) => { + const maxQueryLimit = session?.params?.maxQueryLimit; + if (limit > maxQueryLimit) { + const tips = `${formatMessage({ + id: 'src.component.SQLConfig.5E06ED93', + defaultMessage: '不超过查询条数上限', + })} ${maxQueryLimit}`; + message.error(tips); + } + setLimit(limit || 1); + }} min={1} precision={0} - placeholder={formatMessage({ - id: 'workspace.window.sql.limit.placeholder', - defaultMessage: '1000', - })} + defaultValue={limit} style={{ width: 70, marginLeft: 8, }} + onBlur={() => { + onRefresh(limit); + }} onPressEnter={() => { - onRefresh(limit || 1000); + onRefresh(limit); }} /> @@ -1364,7 +1382,7 @@ const DDLResultSet: React.FC = function (props) { defaultMessage: '刷新', })} icon={} - onClick={onRefresh.bind(this, limit || 1000)} + onClick={onRefresh.bind(this, limit)} /> ) : null}
diff --git a/src/page/Workspace/components/SQLPage/index.tsx b/src/page/Workspace/components/SQLPage/index.tsx index ae70ac8eb..3073db411 100644 --- a/src/page/Workspace/components/SQLPage/index.tsx +++ b/src/page/Workspace/components/SQLPage/index.tsx @@ -843,6 +843,49 @@ export class SQLPage extends Component { sqlStore.unlockResultSet(pageKey, key); }; + public handleCloseOtherResultSets = (currentKey: string) => { + const { sqlStore, pageKey } = this.props; + const resultSets = sqlStore.resultSets.get(pageKey); + + // 关闭其它结果集 + sqlStore.closeOtherResultSets(pageKey, currentKey); + + // 检查当前激活tab是否被关闭,如果被关闭则切换到当前结果集 + const updatedResultSets = sqlStore.resultSets.get(pageKey); + if ( + updatedResultSets && + !updatedResultSets.find((set) => set.uniqKey === sqlStore.activeTab[pageKey]) + ) { + sqlStore.setActiveTab(pageKey, currentKey); + } + + this.triggerTableLayout(); + }; + + public handleCloseAllResultSets = () => { + const { sqlStore, pageKey } = this.props; + + // 关闭所有结果集 + sqlStore.closeAllResultSets(pageKey); + + // 切换到执行记录tab + const updatedResultSets = sqlStore.resultSets.get(pageKey); + if (!updatedResultSets || updatedResultSets.length === 0) { + sqlStore.setActiveTab(pageKey, 'records'); + } else { + // 优先定位到日志tab + const logTab = updatedResultSets.find((set) => set.type === 'LOG'); + if (logTab) { + sqlStore.setActiveTab(pageKey, logTab.uniqKey); + } else { + // 如果没有日志tab,则定位到第一个(固定的结果集) + sqlStore.setActiveTab(pageKey, updatedResultSets[0].uniqKey); + } + } + + this.triggerTableLayout(); + }; + public handleChangeResultSetTab = (activeKey: string) => { const { sqlStore, pageKey } = this.props; sqlStore.setActiveTab(pageKey, activeKey); @@ -1307,6 +1350,8 @@ export class SQLPage extends Component { onCloseResultSet={this.handleCloseResultSet} onLockResultSet={this.handleLockResultSet} onUnLockResultSet={this.handleUnLockResultSet} + onCloseOtherResultSets={this.handleCloseOtherResultSets} + onCloseAllResultSets={this.handleCloseAllResultSets} onExportResultSet={this.handleStartExportResultSet} onShowExecuteDetail={this.handleShowExecuteDetail} onShowTrace={this.handleShowTrace} diff --git a/src/page/Workspace/components/SQLResultSet/index.tsx b/src/page/Workspace/components/SQLResultSet/index.tsx index 6c613d0aa..3fa304189 100644 --- a/src/page/Workspace/components/SQLResultSet/index.tsx +++ b/src/page/Workspace/components/SQLResultSet/index.tsx @@ -31,6 +31,8 @@ import { ModalStore } from '@/store/modal'; import sessionManager from '@/store/sessionManager'; import SessionStore from '@/store/sessionManager/session'; import type { SQLStore } from '@/store/sql'; +import setting from '@/store/setting'; +import { EquerySqlResultDisplayMode } from '@/component/ODCSetting/config/user/database'; import { inject, observer } from 'mobx-react'; import type { MenuInfo } from 'rc-menu/lib/interface'; import DDLResultSet from '../DDLResultSet'; @@ -46,6 +48,9 @@ export const sqlLintTabKey = 'sqlLint'; export const enum MenuKey { LOCK = 'LOCK', UNLOCK = 'UNLOCK', + CLOSE_CURRENT = 'CLOSE_CURRENT', + CLOSE_OTHERS = 'CLOSE_OTHERS', + CLOSE_ALL = 'CLOSE_ALL', } interface IProps { @@ -68,6 +73,8 @@ interface IProps { onExportResultSet: (resultSetIndex: number, limit: number, tableName: string) => void; onLockResultSet?: (key: string) => void; onUnLockResultSet?: (key: string) => void; + onCloseOtherResultSets?: (currentKey: string) => void; + onCloseAllResultSets?: () => void; onShowExecuteDetail: (sql: string, tag: string) => void; hanldeCloseLintPage: () => void; onSubmitRows: ( @@ -105,6 +112,8 @@ const SQLResultSet: React.FC = function (props) { onLockResultSet, onUnLockResultSet, onCloseResultSet, + onCloseOtherResultSets, + onCloseAllResultSets, hanldeCloseLintPage, onUpdateEditing, } = props; @@ -146,10 +155,29 @@ const SQLResultSet: React.FC = function (props) { onUnLockResultSet(key); } break; + case MenuKey.CLOSE_CURRENT: + onCloseResultSet(key); + break; + case MenuKey.CLOSE_OTHERS: + if (onCloseOtherResultSets) { + onCloseOtherResultSets(key); + } + break; + case MenuKey.CLOSE_ALL: + if (onCloseAllResultSets) { + onCloseAllResultSets(); + } + break; default: } }, - [onLockResultSet, onUnLockResultSet], + [ + onLockResultSet, + onUnLockResultSet, + onCloseResultSet, + onCloseOtherResultSets, + onCloseAllResultSets, + ], ); /** @@ -162,6 +190,11 @@ const SQLResultSet: React.FC = function (props) { locked: boolean, resultSetKey: string, ): ReactNode { + // 检查是否为追加模式 + const isAppendMode = + setting.configurations['odc.sqlexecute.querySqlResultDisplayMode'] === + EquerySqlResultDisplayMode.APPEND; + const menu: MenuProps = { style: { width: '160px', @@ -171,22 +204,37 @@ const SQLResultSet: React.FC = function (props) { e.domEvent.stopPropagation(); handleMenuClick(e, resultSetKey); }, - items: [ - { - key: MenuKey.LOCK, - label: formatMessage({ - id: 'workspace.window.sql.record.column.lock', - defaultMessage: '固定', - }), - }, - { - key: MenuKey.UNLOCK, - label: formatMessage({ - id: 'workspace.window.sql.record.column.unlock', - defaultMessage: '解除固定', - }), - }, - ], + items: isAppendMode + ? [ + { + key: MenuKey.CLOSE_CURRENT, + label: '关闭该结果集', + }, + { + key: MenuKey.CLOSE_OTHERS, + label: '关闭其它结果集', + }, + { + key: MenuKey.CLOSE_ALL, + label: '关闭所有结果集', + }, + ] + : [ + { + key: MenuKey.LOCK, + label: formatMessage({ + id: 'workspace.window.sql.record.column.lock', + defaultMessage: '固定', + }), + }, + { + key: MenuKey.UNLOCK, + label: formatMessage({ + id: 'workspace.window.sql.record.column.unlock', + defaultMessage: '解除固定', + }), + }, + ], }; return ( diff --git a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx index b12849b1b..1495106bd 100644 --- a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx +++ b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx @@ -24,8 +24,8 @@ import login from '@/store/login'; import { formatMessage } from '@/util/intl'; import Icon, { ArrowDownOutlined, LoadingOutlined } from '@ant-design/icons'; import { useRequest } from 'ahooks'; -import { Divider, Select, Space } from 'antd'; import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Divider, Flex, Select, Space } from 'antd'; import SessionContext from '../context'; import { DEFALT_WIDTH } from './const'; import { IDatabase } from '@/d.ts/database'; @@ -48,6 +48,9 @@ interface IProps { datasourceMode?: boolean; projectMode?: boolean; onChange?: (value: number, database?: IDatabase) => void; + showProject?: boolean; + popoverWidth?: number; + manageLinkVisible?: boolean; onInit?: (database?: IDatabase) => void; } @@ -68,6 +71,9 @@ const SelectItem: React.FC = ({ isLogicalDatabase = false, datasourceMode = false, projectMode = isLogicalDatabase, + showProject = true, + popoverWidth, + manageLinkVisible = false, onInit, }) => { const { data: database, run: runDatabase } = useRequest(getDatabase, { @@ -163,7 +169,10 @@ const SelectItem: React.FC = ({ } if (!datasourceMode && database?.data) { return ( - + <> = ({ style={{ fontSize: 16, marginRight: 4, verticalAlign: 'textBottom' }} /> - {database?.data?.name} - + +
+ {database?.data?.name} +
+ ); } return placeholder; @@ -199,10 +217,11 @@ const SelectItem: React.FC = ({ projectId={projectId} dataSourceId={dataSourceId} filters={filters} - width={width || DEFALT_WIDTH} + width={popoverWidth || width || DEFALT_WIDTH} taskType={taskType} scheduleType={scheduleType} disabled={disabled} + manageLinkVisible={manageLinkVisible} > } - defaultValue={timeValue || TimeOptions[0].value} - options={TimeOptions} - onChange={(value: number) => { - setTimeValue(value); - localStorage.setItem(cacheTimeKey, JSON.stringify(value)); - }} - /> - {String(timeValue) === 'custom' && ( - 2 ? styles.schedules : styles.schedulesVertical}> + +
+
任务概览
+
+ { - setDateValue(value); - // 将 Dayjs 对象转换为时间戳进行存储 - const dateForStorage = value - ? [value[0]?.valueOf(), value[1]?.valueOf()] - : null; - localStorage.setItem(cacheDateKey, JSON.stringify(dateForStorage)); + setSelectedProjectId(value === -1 ? undefined : value); + localStorage.setItem(cacheProjectIdKey, value + ''); }} /> - )} -
+ = 5 ? '最多可创建 5 个 AccessKey' : ''}> + + + + + + + + ); +}; + +export default AccessKeyManageModal; diff --git a/src/page/Auth/User/component/AccessKeySuccessModal/index.less b/src/page/Auth/User/component/AccessKeySuccessModal/index.less new file mode 100644 index 000000000..1ed511cd1 --- /dev/null +++ b/src/page/Auth/User/component/AccessKeySuccessModal/index.less @@ -0,0 +1,36 @@ +.successModal { + .container { + padding: 8px 0; + .alertTip { + background: #fff5e5; + border: 1px solid #ffd699; + border-radius: 8px; + } + } + + .credentialsBox { + margin-top: 24px; + margin-bottom: 24px; + + .credentialItem { + display: flex; + margin-bottom: 8px; + + &:last-child { + margin-bottom: 0; + } + + .value { + word-break: break-all; + margin-left: 4px; + } + } + } + + .copyButton { + width: 143px; + height: 32px; + font-size: 14px; + border-radius: 8px; + } +} diff --git a/src/page/Auth/User/component/AccessKeySuccessModal/index.tsx b/src/page/Auth/User/component/AccessKeySuccessModal/index.tsx new file mode 100644 index 000000000..4798ff1f5 --- /dev/null +++ b/src/page/Auth/User/component/AccessKeySuccessModal/index.tsx @@ -0,0 +1,100 @@ +/* + * 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 { Alert, Button, Modal, Typography, message } from 'antd'; +import { useState } from 'react'; +import copy from 'copy-to-clipboard'; +import styles from './index.less'; +import { IAccessKey } from '@/d.ts/openAPI'; + +interface IProps { + visible: boolean; + accessKey: IAccessKey | null; + onClose: () => void; +} + +const AccessKeySuccessModal: React.FC = ({ visible, accessKey, onClose }) => { + const [copying, setCopying] = useState(false); + + const handleCopyCredentials = () => { + if (!accessKey) return; + + setCopying(true); + try { + const credentials = `AccessKey ID: ${accessKey.accessKeyId}\nAccessKey Secret: ${accessKey.secretAccessKey}`; + const success = copy(credentials); + if (success) { + message.success('复制成功'); + } else { + message.error('复制失败'); + } + } catch (error) { + console.error('复制失败:', error); + message.error('复制失败'); + } finally { + setCopying(false); + } + }; + + return ( + +
+ + + {accessKey && ( + <> +
+
+ AccessKey ID: + {accessKey.accessKeyId} +
+
+ + AccessKey Secret: + + {accessKey.secretAccessKey} +
+
+ + + + )} +
+
+ ); +}; + +export default AccessKeySuccessModal; diff --git a/src/page/Auth/User/index.tsx b/src/page/Auth/User/index.tsx index 8fbb9bad8..de9a335c9 100644 --- a/src/page/Auth/User/index.tsx +++ b/src/page/Auth/User/index.tsx @@ -46,6 +46,7 @@ import React from 'react'; import { ResourceContext } from '../context'; import DetailContent from './component/DetailContent'; import FormModal from './component/FormModal'; +import AccessKeyManageModal from './component/AccessKeyManageModal'; import styles from './index.less'; import login from '@/store/login'; @@ -60,6 +61,8 @@ interface IState { user: IManagerUser; formModalVisible: boolean; detailModalVisible: boolean; + accessKeyModalVisible: boolean; + selectedUserId: number; } interface IManagerBatchUser extends IManagerUser { @@ -97,6 +100,8 @@ class UserPage extends React.PureComponent { user: null, formModalVisible: false, detailModalVisible: false, + accessKeyModalVisible: false, + selectedUserId: null, }; private getPageColumns = (roles: any[]) => { @@ -123,7 +128,7 @@ class UserPage extends React.PureComponent { title: formatMessage({ id: 'odc.components.UserPage.Role', defaultMessage: '角色' }), // 角色 dataIndex: 'roles', ellipsis: true, - width: 200, + width: 220, key: 'roles', filters: [{ name: , id: 0 }].concat(roles ?? []).map(({ name, id }) => { return { @@ -212,7 +217,7 @@ class UserPage extends React.PureComponent { { title: formatMessage({ id: 'odc.components.UserPage.Operation', defaultMessage: '操作' }), // 操作 - width: 132, + width: 212, key: 'action', fixed: 'right' as FixedType, render: (value, record) => { @@ -254,6 +259,22 @@ class UserPage extends React.PureComponent { + + + { + this.openAccessKeyModal(record.id); + }} + > + 管理 AccessKey + + + ); }, @@ -315,6 +336,20 @@ class UserPage extends React.PureComponent { }); }; + private openAccessKeyModal = (userId: number) => { + this.setState({ + accessKeyModalVisible: true, + selectedUserId: userId, + }); + }; + + private handleCloseAccessKeyModal = () => { + this.setState({ + accessKeyModalVisible: false, + selectedUserId: null, + }); + }; + loadRoles = async () => { const roles = await getRoleList(); this.setState({ @@ -411,8 +446,17 @@ class UserPage extends React.PureComponent { } render() { - const { formModalVisible, detailModalVisible, editId, detailId, users, roles, user } = - this.state; + const { + formModalVisible, + detailModalVisible, + accessKeyModalVisible, + editId, + detailId, + selectedUserId, + users, + roles, + user, + } = this.state; const disabledOp = this.isMe(user); const canAcessCreate = canAcess({ resourceIdentifier: IManagerResourceType.user, @@ -664,6 +708,12 @@ class UserPage extends React.PureComponent { /> )} /> + + ); } From db3f9eff6e5d6d51eb5b5049c3563b380b967e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Mon, 8 Sep 2025 17:47:13 +0800 Subject: [PATCH 047/239] =?UTF-8?q?PullRequest:=20995=20fix=EF=BC=9A?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=86=85=E7=BD=AE=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/202502bugfix_yz of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into cloud/202504 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/995 Reviewed-by: 晓康 * 添加内置选择 --- .../Task/DataArchiveTask/CreateModal/VariableConfig.tsx | 2 +- src/component/Task/DataClearTask/CreateModal/VariableConfig.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/component/Task/DataArchiveTask/CreateModal/VariableConfig.tsx b/src/component/Task/DataArchiveTask/CreateModal/VariableConfig.tsx index 789013b00..1bb889984 100644 --- a/src/component/Task/DataArchiveTask/CreateModal/VariableConfig.tsx +++ b/src/component/Task/DataArchiveTask/CreateModal/VariableConfig.tsx @@ -82,7 +82,7 @@ export const timeUnitOptions = [ ]; const ENABLE_PATTERN_OPERATOR = false; -const timeFormatOptions = ['yyyy-MM-dd', 'yyyyMMdd'].map((item) => ({ +const timeFormatOptions = ['yyyy-MM-dd', 'yyyyMMdd', 'yyyy-MM-01'].map((item) => ({ label: item, value: item, })); diff --git a/src/component/Task/DataClearTask/CreateModal/VariableConfig.tsx b/src/component/Task/DataClearTask/CreateModal/VariableConfig.tsx index 45e8e61b9..25cb50207 100644 --- a/src/component/Task/DataClearTask/CreateModal/VariableConfig.tsx +++ b/src/component/Task/DataClearTask/CreateModal/VariableConfig.tsx @@ -24,7 +24,7 @@ import { timeUnitOptions } from '../../DataArchiveTask/CreateModal/VariableConfi import { variable } from './index'; import styles from './index.less'; const ENABLE_PATTERN_OPERATOR = false; -const timeFormatOptions = ['yyyy-MM-dd', 'yyyyMMdd'].map((item) => ({ +const timeFormatOptions = ['yyyy-MM-dd', 'yyyyMMdd', 'yyyy-MM-01'].map((item) => ({ label: item, value: item, })); From 50512551d8110726a7ff416a31d7e571801dfdd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Tue, 9 Sep 2025 14:26:20 +0800 Subject: [PATCH 048/239] =?UTF-8?q?PullRequest:=20996=20fix:=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dtab=E9=97=B4=E9=85=8D=E7=BD=AE=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/broadcast of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/996 Reviewed-by: 晓康 * fix: 修复broadCast --- src/util/makeDataShareable.ts | 70 +++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/src/util/makeDataShareable.ts b/src/util/makeDataShareable.ts index dc53c7203..be2fe4e9b 100644 --- a/src/util/makeDataShareable.ts +++ b/src/util/makeDataShareable.ts @@ -44,6 +44,19 @@ class ShareableDataManager { private channels = new Map(); private subscriptions = new Map void>>(); private channelIdentifierTypes = new Map(); + /** + * 安全序列化数据,确保可以被 BroadcastChannel 克隆 + */ + private safeSerialize(data: any): any { + try { + // 使用 JSON 序列化和反序列化来确保数据可克隆 + return JSON.parse(JSON.stringify(data)); + } catch (error) { + console.warn('[makeDataShareable] Failed to serialize data:', error); + // 如果序列化失败,返回 null 并记录警告 + return null; + } + } /** * 获取或创建BroadcastChannel @@ -115,9 +128,16 @@ class ShareableDataManager { return; } + // 使用 safeSerialize 确保数据可以被安全传输 + const safeData = this.safeSerialize(data); + if (safeData === null) { + // 如果序列化失败,直接返回,不发送消息 + return; + } + const message: any = { type: 'data_update', - data, + data: safeData, timestamp: Date.now(), }; @@ -222,10 +242,29 @@ export function makeDataShareable( (newData) => { if (isUpdating) return; // 避免循环更新 - if (newData !== null && newData !== target[propertyKey]) { - isUpdating = true; - target[propertyKey] = newData; - isUpdating = false; + if (newData !== null) { + // 使用深度比较来检查数据是否真正发生变化 + try { + const newSerialized = JSON.stringify(newData); + const currentSerialized = JSON.stringify(target[propertyKey]); + + if (newSerialized !== currentSerialized) { + isUpdating = true; + target[propertyKey] = newData; + isUpdating = false; + } + } catch (error) { + // 如果序列化失败,回退到引用比较 + console.warn( + '[makeDataShareable] Failed to compare data, falling back to reference comparison:', + error, + ); + if (newData !== target[propertyKey]) { + isUpdating = true; + target[propertyKey] = newData; + isUpdating = false; + } + } } }, identifierType, @@ -234,10 +273,27 @@ export function makeDataShareable( // 监听本地属性变化并广播 const disposeReaction = reaction( () => target[propertyKey], - (newValue) => { + (newValue, previousValue) => { if (isUpdating) return; // 避免循环广播 - shareableManager.broadcastUpdate(channelName, newValue); + // 使用深度比较来检查数据是否真正发生变化 + try { + const newSerialized = JSON.stringify(newValue); + const prevSerialized = JSON.stringify(previousValue); + + if (newSerialized !== prevSerialized) { + shareableManager.broadcastUpdate(channelName, newValue); + } + } catch (error) { + // 如果序列化失败,回退到引用比较 + console.warn( + '[makeDataShareable] Failed to compare values, falling back to reference comparison:', + error, + ); + if (newValue !== previousValue) { + shareableManager.broadcastUpdate(channelName, newValue); + } + } }, ); From 2775184f08d0ecda1ca3ede4b3689588732c4ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Tue, 9 Sep 2025 15:31:37 +0800 Subject: [PATCH 049/239] =?UTF-8?q?PullRequest:=20997=20=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E5=8F=8A=E5=9B=BE=E8=A1=A8=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E8=AF=AD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/pieChatTooltip of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/997 Reviewed-by: 晓康 * fix: 饼图提示语优先展示在左侧 * fix: bar 图提示语可点击 * fix: 完善工单跳转过滤 * feat: task 跳转sider 定位 --- .../Task/hooks/useTaskSearchParams.tsx | 13 +- src/component/Task/layout/Content.tsx | 26 +++- .../Console/components/BarChart/index.less | 17 +++ .../Console/components/BarChart/index.tsx | 118 +++++++++++++++++- .../Console/components/DonutChart/index.tsx | 26 ++-- src/page/Console/index.tsx | 7 +- 6 files changed, 187 insertions(+), 20 deletions(-) diff --git a/src/component/Task/hooks/useTaskSearchParams.tsx b/src/component/Task/hooks/useTaskSearchParams.tsx index 4613521fd..f0b9a6a93 100644 --- a/src/component/Task/hooks/useTaskSearchParams.tsx +++ b/src/component/Task/hooks/useTaskSearchParams.tsx @@ -9,12 +9,17 @@ const useTaskSearchParams = () => { const defaultTaskId = searchParams.get('taskId'); const defaultTaskType = searchParams.get('taskType') as TaskType; + const taskTypesStr = searchParams.get('taskTypes'); + const resolvedDefaultTaskType = + defaultTaskType || (taskTypesStr ? (taskTypesStr.split(',')[0] as TaskType) : null); const defaultOrganizationId = searchParams.get('organizationId'); const defaultTab = searchParams.get('tab') as TaskTab; const timeValue = searchParams.get('timeValue'); + const timeRange = searchParams.get('timeRange'); const startTime = searchParams.get('startTime'); const endTime = searchParams.get('endTime'); const projectId = searchParams.get('projectId'); + const statusesStr = searchParams.get('statuses'); const currentOrganizationId = login.organizationId; const isOrganizationMatch = toInteger(defaultOrganizationId) === toInteger(currentOrganizationId); @@ -26,9 +31,12 @@ const useTaskSearchParams = () => { searchParams.delete('tab'); // Delete filter parameters passed from Console searchParams.delete('timeValue'); + searchParams.delete('timeRange'); searchParams.delete('startTime'); searchParams.delete('endTime'); searchParams.delete('projectId'); + searchParams.delete('taskTypes'); + searchParams.delete('statuses'); setSearchParams(searchParams); }); }; @@ -36,12 +44,15 @@ const useTaskSearchParams = () => { return { searchParams: { defaultTaskId: isOrganizationMatch ? toInteger(defaultTaskId) : null, - defaultTaskType, + defaultTaskType: resolvedDefaultTaskType, defaultTab, timeValue: timeValue ? (isNaN(Number(timeValue)) ? timeValue : Number(timeValue)) : null, + timeRange: timeRange ? (isNaN(Number(timeRange)) ? timeRange : Number(timeRange)) : null, startTime: startTime ? Number(startTime) : null, endTime: endTime ? Number(endTime) : null, projectId: projectId ? Number(projectId) : null, + taskTypes: taskTypesStr ? taskTypesStr.split(',') : null, + statuses: statusesStr ? statusesStr.split(',') : null, }, resetSearchParams, }; diff --git a/src/component/Task/layout/Content.tsx b/src/component/Task/layout/Content.tsx index 4a8b30b48..f718131bc 100644 --- a/src/component/Task/layout/Content.tsx +++ b/src/component/Task/layout/Content.tsx @@ -28,6 +28,7 @@ import { IPagination, ITaskParam, TaskPageMode, TaskTab } from '@/component/Task import { IState } from '@/component/Task/interface'; import ApprovalModal from '@/component/Task/component/ApprovalModal'; import { getDefaultParam, getFirstEnabledTask } from '../helper'; +import { TaskConfig } from '@/common/task'; import useTaskSearchParams from '../hooks/useTaskSearchParams'; interface IProps { @@ -55,9 +56,12 @@ const Content: React.FC = (props) => { defaultTaskType, defaultTab, timeValue, + timeRange, startTime, endTime, projectId: urlProjectId, + taskTypes: urlTaskTypes, + statuses: urlStatuses, }, resetSearchParams, } = useTaskSearchParams(); @@ -205,7 +209,15 @@ const Content: React.FC = (props) => { const resolveUrlSearchParams = async () => { defaultTaskId && (await openDefaultTask()); if (defaultTaskType) { - taskStore.changeTaskPageType(defaultTaskType as undefined as TaskPageType); + // 将 TaskType 映射到对应的 TaskPageType + const taskConfig = TaskConfig[defaultTaskType]; + const taskPageType = taskConfig?.pageType; + if (taskPageType) { + taskStore.changeTaskPageType(taskPageType); + } else { + const firstEnabledTask = getFirstEnabledTask(); + taskStore.changeTaskPageType(firstEnabledTask?.pageType); + } } else { const firstEnabledTask = getFirstEnabledTask(); taskStore.changeTaskPageType(firstEnabledTask?.pageType); @@ -219,6 +231,8 @@ const Content: React.FC = (props) => { // Apply time filter from URL if (timeValue !== null) { newParams.timeRange = timeValue; + } else if (timeRange !== null) { + newParams.timeRange = timeRange; } // Apply custom date range from URL @@ -232,6 +246,16 @@ const Content: React.FC = (props) => { newParams.projectId = [String(urlProjectId)]; } + // Apply task types filter from URL + if (urlTaskTypes !== null && urlTaskTypes.length > 0) { + newParams.taskTypes = urlTaskTypes; + } + + // Apply task statuses filter from URL + if (urlStatuses !== null && urlStatuses.length > 0) { + newParams.taskStatus = urlStatuses; + } + setParams(newParams); resetSearchParams(); }; diff --git a/src/page/Console/components/BarChart/index.less b/src/page/Console/components/BarChart/index.less index 5de15f5e9..785d65f07 100644 --- a/src/page/Console/components/BarChart/index.less +++ b/src/page/Console/components/BarChart/index.less @@ -40,6 +40,14 @@ display: flex; align-items: center; justify-content: space-between; + cursor: pointer; + padding: 4px; + border-radius: 4px; + transition: background-color 0.3s ease; + + &:hover { + background-color: rgba(24, 144, 255, 0.1); + } &-number { gap: 4px; @@ -54,6 +62,15 @@ align-items: center; margin: 4px 0; font-size: 12px; + cursor: pointer; + padding: 4px; + border-radius: 4px; + transition: background-color 0.2s ease; + + &:hover { + background-color: rgba(24, 144, 255, 0.1); + } + &-dot { width: 8px; height: 8px; diff --git a/src/page/Console/components/BarChart/index.tsx b/src/page/Console/components/BarChart/index.tsx index bdc94dc6b..f81bbd21e 100644 --- a/src/page/Console/components/BarChart/index.tsx +++ b/src/page/Console/components/BarChart/index.tsx @@ -3,10 +3,29 @@ import * as echarts from 'echarts'; import { ConsoleTextConfig, TaskTitle, TaskTypes } from '../../const'; import './index.less'; import { PersonalizeLayoutContext } from '@/page/Console/PersonalizeLayoutContext'; +import { useNavigate } from '@umijs/max'; -const BarChart = ({ data }) => { +const BarChart = ({ data, selectedProjectId, timeValue, dateValue }) => { const { status, statusColor, statusType } = ConsoleTextConfig.schdules; const chartRef = useRef(null); + const navigate = useNavigate(); + + // 状态映射:Console状态 -> 任务页面状态 + const statusMapping = { + PENDING: ['APPROVING', 'WAIT_FOR_EXECUTION'], + EXECUTING: ['EXECUTING'], + EXECUTION_SUCCESS: ['EXECUTION_SUCCEEDED', 'EXECUTION_SUCCEEDED_WITH_ERRORS', 'COMPLETED'], + EXECUTION_FAILURE: [ + 'REJECTED', + 'EXECUTION_FAILED', + 'ROLLBACK_FAILED', + 'CANCELLED', + 'APPROVAL_EXPIRED', + 'WAIT_FOR_EXECUTION_EXPIRED', + 'EXECUTION_EXPIRED', + ], + OTHER: ['ROLLBACK_SUCCEEDED'], + }; const { checkedKeys: allCheckedKeys, getOrderedTaskTypes, @@ -42,12 +61,42 @@ const BarChart = ({ data }) => { axisPointer: { type: 'shadow', }, + enterable: true, + hideDelay: 300, + position: function (point, params, dom, rect, size) { + const [mouseX, mouseY] = point; + const { contentSize, viewSize } = size; + const [contentWidth, contentHeight] = contentSize; + const [viewWidth, viewHeight] = viewSize; + + // 计算固定位置 - 在柱状图右侧或上方 + let x = mouseX + 20; // 距离鼠标右侧20px + let y = mouseY - contentHeight - 10; // 距离鼠标上方10px + + // 如果tooltip会超出右边界,则放到左侧 + if (x + contentWidth > viewWidth) { + x = mouseX - contentWidth - 20; + } + + // 如果tooltip会超出上边界,则放到下方 + if (y < 0) { + y = mouseY + 10; + } + + // 如果tooltip会超出下边界,则上移 + if (y + contentHeight > viewHeight) { + y = viewHeight - contentHeight - 10; + } + + return [x, y]; + }, formatter: function (params) { let total = 0; params.forEach((item) => { total += item.value; }); + const taskType = checkedKeys[params[0].dataIndex]; let result = `
`; // 标题 @@ -56,7 +105,7 @@ const BarChart = ({ data }) => { // 任务总计 result += total > 0 - ? `
+ ? `
任务总计 ${total} > @@ -67,8 +116,12 @@ const BarChart = ({ data }) => { params.forEach((item) => { if (item.value > 0) { result += ` -
-
+
+
${item.seriesName} ${item.value} > @@ -138,6 +191,57 @@ const BarChart = ({ data }) => { chart.setOption(option); + const handleTooltipClick = (event) => { + const target = event.target; + if (!target) return; + + let clickableElement = target.closest('[data-click-type]'); + if (!clickableElement) return; + + event.preventDefault(); + event.stopPropagation(); + + const taskType = clickableElement.getAttribute('data-task-type'); + const clickType = clickableElement.getAttribute('data-click-type'); + const status = clickableElement.getAttribute('data-status'); + + if (taskType) { + let url = '/task'; + const searchParams = new URLSearchParams(); + + searchParams.append('taskTypes', taskType); + + if (clickType === 'detail' && status) { + // 使用状态映射将Console状态转换为任务页面状态 + const mappedStatuses = statusMapping[status] || [status]; + searchParams.append('statuses', mappedStatuses.join(',')); + } + + if (selectedProjectId !== undefined) { + searchParams.append('projectId', selectedProjectId.toString()); + } + + if (dateValue && Array.isArray(dateValue) && dateValue.length === 2) { + searchParams.append('startTime', dateValue[0].valueOf().toString()); + searchParams.append('endTime', dateValue[1].valueOf().toString()); + } else if (timeValue !== undefined) { + searchParams.append('timeRange', timeValue.toString()); + } + + const queryString = searchParams.toString(); + if (queryString) { + url += `?${queryString}`; + } + + navigate(url); + } + }; + + const tooltipContainer = chart.getDom(); + if (tooltipContainer) { + tooltipContainer.addEventListener('click', handleTooltipClick); + } + // 自适应大小 const resizeObserver = new ResizeObserver(() => { chart.resize(); @@ -145,11 +249,15 @@ const BarChart = ({ data }) => { resizeObserver.observe(chartRef.current); return () => { + // 清理事件监听器 + if (tooltipContainer) { + tooltipContainer.removeEventListener('click', handleTooltipClick); + } resizeObserver.disconnect(); chart.dispose(); }; } - }, [data, checkedKeys]); + }, [data, checkedKeys, selectedProjectId, timeValue, dateValue, navigate]); return
; }; diff --git a/src/page/Console/components/DonutChart/index.tsx b/src/page/Console/components/DonutChart/index.tsx index 1c3dc7c16..95e1723db 100644 --- a/src/page/Console/components/DonutChart/index.tsx +++ b/src/page/Console/components/DonutChart/index.tsx @@ -42,19 +42,19 @@ const PieChart = ({ progress }) => { const [contentWidth, contentHeight] = contentSize; // Tooltip 的宽高 const [viewWidth, viewHeight] = viewSize; // 视图宽高 - let x = mouseX + 10; // 在右侧 10px 开始 + let x = mouseX - contentWidth - 10; // 在左侧 10px 开始 let y = mouseY - contentHeight - 10; // 在上方 10px 开始 - // 如果 tooltip 会超出屏幕右侧,则调整到左侧 - if (x + contentWidth > viewWidth) { - x = mouseX - contentWidth - 10; - } - // 如果 tooltip 会超出屏幕左侧,则调整到右侧 if (x < 0) { x = mouseX + 10; } + // 如果 tooltip 会超出屏幕右侧,则调整到左侧 + if (x + contentWidth > viewWidth) { + x = mouseX - contentWidth - 10; + } + // 如果 tooltip 会超出屏幕顶部,则下移 if (y < 0) { y = mouseY + 10; @@ -77,17 +77,19 @@ const PieChart = ({ progress }) => { const offsetX = Math.cos(angle) * radius; const offsetY = Math.sin(angle) * radius; - // 计算 tooltip 的位置 - x = centerX + offsetX + 10; - y = centerY + offsetY + 10; + // 优先计算左侧位置 + x = centerX + offsetX - contentWidth - 10; + y = centerY + offsetY - contentHeight / 2; // 检查 tooltip 是否超出视图边界并调整位置 - if (x + contentWidth > viewWidth) { - x = centerX + offsetX - contentWidth - 10; - } if (x < 0) { + // 如果左侧放不下,则放在右侧 x = centerX + offsetX + 10; } + if (x + contentWidth > viewWidth) { + // 如果右侧也放不下,则放在左侧并允许部分超出 + x = centerX + offsetX - contentWidth - 10; + } if (y + contentHeight > viewHeight) { y = centerY + offsetY - contentHeight - 10; } diff --git a/src/page/Console/index.tsx b/src/page/Console/index.tsx index c57b53bc3..600f3f2a4 100644 --- a/src/page/Console/index.tsx +++ b/src/page/Console/index.tsx @@ -344,7 +344,12 @@ const ConsoleMain = () => { > {hasTaskChecked && (
- +
)} {hasTaskChecked && From d33aa3861c5bb9a70c636f27556ff92b8ae3ddcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Tue, 9 Sep 2025 15:57:43 +0800 Subject: [PATCH 050/239] =?UTF-8?q?PullRequest:=20998=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=B7=A5=E5=8D=95=E7=8A=B6=E6=80=81=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/taskStatus of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/998 Reviewed-by: 晓康 * fix: 修复工单执行失败对应的状态 * fix: 调整其他状态 --- src/page/Console/components/BarChart/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/page/Console/components/BarChart/index.tsx b/src/page/Console/components/BarChart/index.tsx index f81bbd21e..3ad062849 100644 --- a/src/page/Console/components/BarChart/index.tsx +++ b/src/page/Console/components/BarChart/index.tsx @@ -12,15 +12,15 @@ const BarChart = ({ data, selectedProjectId, timeValue, dateValue }) => { // 状态映射:Console状态 -> 任务页面状态 const statusMapping = { - PENDING: ['APPROVING', 'WAIT_FOR_EXECUTION'], + PENDING: ['CREATED', 'APPROVING', 'WAIT_FOR_EXECUTION', 'WAIT_FOR_CONFIRM'], EXECUTING: ['EXECUTING'], - EXECUTION_SUCCESS: ['EXECUTION_SUCCEEDED', 'EXECUTION_SUCCEEDED_WITH_ERRORS', 'COMPLETED'], + EXECUTION_SUCCESS: ['EXECUTION_SUCCEEDED', 'COMPLETED'], EXECUTION_FAILURE: [ 'REJECTED', - 'EXECUTION_FAILED', - 'ROLLBACK_FAILED', - 'CANCELLED', 'APPROVAL_EXPIRED', + 'EXECUTION_FAILED', + 'EXECUTION_ABNORMAL', + 'PRE_CHECK_FAILED', 'WAIT_FOR_EXECUTION_EXPIRED', 'EXECUTION_EXPIRED', ], From b98549a885c4c2d38db0891c7fad9274806a3e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Wed, 10 Sep 2025 14:50:17 +0800 Subject: [PATCH 051/239] =?UTF-8?q?PullRequest:=201000=20fix:=20=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E6=96=87=E6=A1=A3=E5=B1=95=E7=A4=BA=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/docPosition of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/1000 Reviewed-by: 晓康 * fix: 调整文档展示位置 --- src/page/Console/index.less | 2 + src/page/Console/index.tsx | 80 ++++++++++++++++++------------------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/page/Console/index.less b/src/page/Console/index.less index 37070ed30..27f579ba1 100644 --- a/src/page/Console/index.less +++ b/src/page/Console/index.less @@ -234,6 +234,8 @@ .practice, .practiceLargeVerion { height: 332px; + + margin-bottom: 16px; :global { .ant-card-body { padding: 16px; diff --git a/src/page/Console/index.tsx b/src/page/Console/index.tsx index 600f3f2a4..896c991b0 100644 --- a/src/page/Console/index.tsx +++ b/src/page/Console/index.tsx @@ -428,6 +428,46 @@ const ConsoleMain = () => { : styles.docWrapperVertical } > + {boardVisible[ELayoutKey.BestPractices] && ( + +
+ {formatMessage({ id: 'src.page.Console.41EC22B4', defaultMessage: '最佳实践' })} + + { + window.open( + odc.appConfig.docs.url + ? getOBDocsUrl('100.sql-development-common-techniques.html') + : getLocalDocs('100.sql-development-common-techniques.html'), + ); + }} + > + {formatMessage({ id: 'src.page.Console.E60EAE10', defaultMessage: '更多 >' })} + +
+ {articles?.map((article) => { + return ( +
{ + window.open( + odc.appConfig.docs.url + ? getOBDocsUrl(article.fragmentIdentifier) + : getLocalDocs(article.fragmentIdentifier), + ); + }} + > + {article.title} +
+ ); + })} +
+ )} {boardVisible[ELayoutKey.AboutUs] && ( {
)} - {boardVisible[ELayoutKey.BestPractices] && ( - -
- {formatMessage({ id: 'src.page.Console.41EC22B4', defaultMessage: '最佳实践' })} - - { - window.open( - odc.appConfig.docs.url - ? getOBDocsUrl('100.sql-development-common-techniques.html') - : getLocalDocs('100.sql-development-common-techniques.html'), - ); - }} - > - {formatMessage({ id: 'src.page.Console.E60EAE10', defaultMessage: '更多 >' })} - -
- {articles?.map((article) => { - return ( -
{ - window.open( - odc.appConfig.docs.url - ? getOBDocsUrl(article.fragmentIdentifier) - : getLocalDocs(article.fragmentIdentifier), - ); - }} - > - {article.title} -
- ); - })} -
- )}
From 5aa6e2aca17f185dfb0446c8a16f5d64c80bc6a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Wed, 10 Sep 2025 15:09:48 +0800 Subject: [PATCH 052/239] =?UTF-8?q?PullRequest:=20999=20fix:=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=BF=87=E6=BB=A4=E9=A1=B9tooltip=E4=B8=AD=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E6=8F=90=E7=A4=BA=E7=9A=84=E4=BE=9D=E8=B5=96=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/filterTooltip of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/999 Reviewed-by: 晓康 * fix: 修复过滤项tooltip中时间提示的依赖项 --- src/component/Task/layout/Header/Filter/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/component/Task/layout/Header/Filter/index.tsx b/src/component/Task/layout/Header/Filter/index.tsx index 78b407193..e9dfe5d3a 100644 --- a/src/component/Task/layout/Header/Filter/index.tsx +++ b/src/component/Task/layout/Header/Filter/index.tsx @@ -124,7 +124,7 @@ const Filter: React.FC = () => {
); - }, [executeDate]); + }, [executeDate, timeRange]); const tipContent = () => { return ( From 4d0eee1ca91cdae8e5fb934016fad9a6b8d1ff12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Thu, 11 Sep 2025 09:58:39 +0800 Subject: [PATCH 053/239] =?UTF-8?q?PullRequest:=201002=20fix:=20=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=BA=90=E5=90=8D=E7=A7=B0=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/tableTooltip of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/1002 Reviewed-by: 晓康 * fix: 数据源名称展示 --- .../components/RecentlyDatabase/index.tsx | 128 ++++++++++-------- 1 file changed, 71 insertions(+), 57 deletions(-) diff --git a/src/page/Console/components/RecentlyDatabase/index.tsx b/src/page/Console/components/RecentlyDatabase/index.tsx index 3dbdc1912..efc787b85 100644 --- a/src/page/Console/components/RecentlyDatabase/index.tsx +++ b/src/page/Console/components/RecentlyDatabase/index.tsx @@ -172,17 +172,27 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { case EDatabaseTableColumnKey.Recently: const databaseStyle = getDataSourceStyleByConnectType(record?.dataSource?.type); const existed = record?.existed; + + // 获取权限相关的提示内容 + const databasePermissionTooltip = renderTooltipContent({ + type: needToApplyProjectAuth + ? ETootipType.PROJECT + : needToApplyDatabaseAuth + ? ETootipType.DATABASE + : '', + record, + }); + + // 外层 Tooltip 优先显示禁用原因,其次显示权限提示,最后显示数据库名称 + const outerTooltipTitle = forbiddenAccessSQLConsole + ? formatMessage({ + id: 'src.page.Console.components.RecentlyDatabase.806E294C', + defaultMessage: '对象存储暂不支持打开 SQL 控制台', + }) + : databasePermissionTooltip || value; + return ( - +
= ({ modalStore }) => { { + if (!existed || disabledAllOperations || needToApplyDatabaseAuth) { + return; + } + gotoSQLWorkspace( + record?.project?.id, + record?.dataSource?.id, + record?.id, + null, + '', + isLogicalDatabase(record), + ); + }} + style={{ + display: 'inline-block', + maxWidth: '100%', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + cursor: + !existed || disabledAllOperations || needToApplyDatabaseAuth + ? 'default' + : 'pointer', + }} > - { - if (!existed || disabledAllOperations || needToApplyDatabaseAuth) { - return; - } - gotoSQLWorkspace( - record?.project?.id, - record?.dataSource?.id, - record?.id, - null, - '', - isLogicalDatabase(record), - ); - }} - > - {value} - {!existed && ( - - )} - - + {value} + {!existed && ( + + )} + } icon={ record?.type === 'LOGICAL' ? ( @@ -270,6 +278,19 @@ const RecentlyDatabase: React.FC = ({ modalStore }) => { ); } + // 获取权限相关的提示内容 + const permissionTooltip = renderTooltipContent({ + type: needToApplyProjectAuth + ? ETootipType.PROJECT + : needToApplyDatabaseAuth + ? ETootipType.DATABASE + : '', + record, + }); + + // 如果有权限提示,显示权限提示;否则显示完整的数据源名称 + const tooltipTitle = permissionTooltip || value; + return (
= ({ modalStore }) => { label={ {value} From a037ea19eccfe0ffce4fa07b082807338c25d73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Thu, 11 Sep 2025 17:35:43 +0800 Subject: [PATCH 054/239] =?UTF-8?q?PullRequest:=201003=20fix:=20=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=AE=BE=E7=BD=AE=E6=8C=89userId=20=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 'fix/console-drag of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/1003 Reviewed-by: 晓康 * fix: 自定义设置按userId 存储 --- src/page/Console/PersonalizeLayoutContext.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/page/Console/PersonalizeLayoutContext.tsx b/src/page/Console/PersonalizeLayoutContext.tsx index 300100791..3a8f22192 100644 --- a/src/page/Console/PersonalizeLayoutContext.tsx +++ b/src/page/Console/PersonalizeLayoutContext.tsx @@ -28,11 +28,11 @@ export const PersonalizeLayoutProvider: React.FC children, }) => { const [checkedKeys, setCheckedKeys] = useLocalStorageState( - `personalizeLayoutCheckedKeys-${login.organizationId}`, + `personalizeLayoutCheckedKeys-${login.user?.id}`, ); const [treeData, setTreeData] = useLocalStorageState( - `personalizeLayoutTreeData-${login.organizationId}`, + `personalizeLayoutTreeData-${login.user?.id}`, ); // Helper function to extract ordered types from tree data From 5d1d72a999c5a5f00e26fc66a4daea11cedc1cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Fri, 12 Sep 2025 19:20:53 +0800 Subject: [PATCH 055/239] =?UTF-8?q?PullRequest:=20988=20fix:=20441bugfix?= =?UTF-8?q?=E3=80=81=E8=AE=BE=E8=AE=A1=E9=AA=8C=E6=94=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'fix/441bugfix of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/988 Reviewed-by: 晓康 * fix: 作业调整样式,项目打开管理权限页面显示三个接口,逻辑库白屏建表的 sql 语句没有自动填充到发起的数据库变更任务中 * fix: 逻辑库bugfix * fix: 作业名称输入空格可以通过校验 * fix: 验收问题修复 && bugfix * fix: 作业视角搜索添加数据库搜索条件 * fix: 克隆没有克隆到作业的周期时间 * fix: 克隆作业时,给作业名字加上克隆标识前缀 * feat: 修改样式 * fix: 441bugfix * fix: 作业视角非全部需要高亮,执行视角非最近7天需要高亮 --- src/component/ConnectionPopover/index.less | 1 + src/component/ConnectionPopover/index.tsx | 14 ++++- .../components/Actions/ScheduleActions.tsx | 50 ++++-------------- .../ExecutionInfoContainer/index.tsx | 5 +- .../components/PartitionPlanHeader.tsx | 51 ++++++++++--------- .../components/OperationRecord/index.less | 11 +++- .../components/ScheduleDetailModal/index.less | 5 ++ .../components/ScheduleDetailModal/index.tsx | 6 ++- .../components/ScheduleStatusLabel/index.tsx | 8 ++- .../ScheduleTable/DatabaseColumn.tsx | 3 ++ .../components/ScheduleTable/index.tsx | 45 +++++++++++----- src/component/Schedule/interface.ts | 2 +- src/component/Schedule/layout/Content.tsx | 1 + .../Schedule/layout/Header/DateSelect.tsx | 4 +- .../Schedule/layout/Header/Filter/index.tsx | 40 ++++++++++----- .../Schedule/layout/Header/Search.tsx | 1 + .../Schedule/layout/Header/Segment.tsx | 2 + .../Schedule/layout/Header/index.less | 8 +++ .../modals/DataArchive/Create/index.tsx | 5 +- .../modals/DataClear/Create/index.tsx | 5 +- .../modals/PartitionPlan/Create/index.tsx | 5 +- .../Schedule/modals/SQLPlan/Create/index.tsx | 17 ++----- .../CreateTaskConfirmModal/helper.tsx | 10 ++++ .../Task/component/ExecuteFailTip/index.tsx | 8 ++- .../component/PartitionPolicyTable/index.tsx | 2 +- .../Task/component/SQLPreviewModal/index.tsx | 11 +++- .../TaskProgress/MultipAsyncExecute/index.tsx | 4 +- .../TaskProgress/TaskProgressHeader.tsx | 4 +- .../Task/component/TaskTable/index.tsx | 1 + src/component/Task/context/ParamsContext.tsx | 3 +- .../Task/layout/Header/DateSelect.tsx | 4 +- .../Task/layout/Header/Filter/index.tsx | 10 ++-- .../CreateModal/index.tsx | 6 ++- src/constant/schedule.ts | 5 +- src/constant/task.ts | 2 +- src/d.ts/schedule.ts | 4 +- .../User/ManageModal/Database/index.tsx | 9 ++-- 37 files changed, 216 insertions(+), 156 deletions(-) diff --git a/src/component/ConnectionPopover/index.less b/src/component/ConnectionPopover/index.less index 4f5f8a1c7..7610c5171 100644 --- a/src/component/ConnectionPopover/index.less +++ b/src/component/ConnectionPopover/index.less @@ -12,5 +12,6 @@ } .content { flex: 1; + font-weight: normal; word-break: break-word; } diff --git a/src/component/ConnectionPopover/index.tsx b/src/component/ConnectionPopover/index.tsx index c6d123269..0839d09d4 100644 --- a/src/component/ConnectionPopover/index.tsx +++ b/src/component/ConnectionPopover/index.tsx @@ -293,7 +293,13 @@ const ConnectionPopover: React.FC<{ defaultMessage: '类型:{ConnectTypeTextType}', }, - { ConnectTypeTextType: {ConnectTypeText(type)} }, + { + ConnectTypeTextType: ( + + {ConnectTypeText(type)} + + ), + }, ) /*类型:{ConnectTypeTextType}*/ @@ -375,7 +381,11 @@ const ConnectionPopover: React.FC<{ }, { - connectionDbUser: {connection?.username ?? '-'}, + connectionDbUser: ( + + {connection?.username ?? '-'} + + ), }, ) diff --git a/src/component/Schedule/components/Actions/ScheduleActions.tsx b/src/component/Schedule/components/Actions/ScheduleActions.tsx index 6c0f7e679..31e6d125b 100644 --- a/src/component/Schedule/components/Actions/ScheduleActions.tsx +++ b/src/component/Schedule/components/Actions/ScheduleActions.tsx @@ -130,12 +130,7 @@ const ScheduleActions: React.FC = (props) => { title: `确定要终止${scheduleTypeText}吗`, content: ( <> -
- {formatMessage({ - id: 'src.component.Task.component.ActionBar.5E24502A', - defaultMessage: '任务终止后将不可恢复', - })} -
+
作业终止后将不可恢复
), cancelText: formatMessage({ @@ -148,7 +143,7 @@ const ScheduleActions: React.FC = (props) => { setActiveBtnKey(ScheduleActionsEnum.STOP); const res = await terminateSchedule(scheduleId); if (res?.data) { - message.success('任务需要重新审批,审批通过后此任务将终止'); + message.success('作业务需要重新审批,审批通过后此作业将终止'); onReloadList?.(); resetActiveBtnKey(); } else { @@ -174,14 +169,7 @@ const ScheduleActions: React.FC = (props) => { { TaskTypeMapTaskType: ScheduleTextMap[schedule?.type] }, )}
-
- { - formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.TheTaskNeedsToBe', - defaultMessage: '任务需要重新审批,审批通过后此任务将禁用', - }) /*任务需要重新审批,审批通过后此任务将禁用*/ - } -
+
作业需要重新审批,审批通过后此作业将禁用
), cancelText: formatMessage({ @@ -194,12 +182,7 @@ const ScheduleActions: React.FC = (props) => { setActiveBtnKey(ScheduleActionsEnum.DISABLE); const res = await pauseSchedule(scheduleId); if (res?.data) { - message.success( - formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.TheTaskNeedsToBe', - defaultMessage: '任务需要重新审批,审批通过后此任务将禁用', - }), - ); + message.success('提交成功'); onReloadList?.(); resetActiveBtnKey(); } else { @@ -224,12 +207,7 @@ const ScheduleActions: React.FC = (props) => { }) /*启用 SQL 计划*/ }
-
- {formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.TheTaskNeedsToBe.1', - defaultMessage: '任务需要重新审批,审批通过后此任务将启用', - })} -
+
作业需要重新审批,审批通过后此作业将启用
), cancelText: formatMessage({ @@ -242,12 +220,7 @@ const ScheduleActions: React.FC = (props) => { setActiveBtnKey(ScheduleActionsEnum.ENABLE); const res = await resumeSchedule(scheduleId); if (res?.data) { - message.success( - formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.TheTaskNeedsToBe.1', - defaultMessage: '任务需要重新审批,审批通过后此任务将启用', - }), - ); + message.success('提交成功'); onReloadList?.(); resetActiveBtnKey(); } else { @@ -353,7 +326,7 @@ const ScheduleActions: React.FC = (props) => { if (hasPartitionPlanTableConfigs?.length) { const count = hasPartitionPlanTableConfigs?.length; Modal.confirm({ - title: `当前任务中有 ${count} 张表存在有效的分区策略,仅克隆未配置表的分区策略`, + title: `当前作业中有 ${count} 张表存在有效的分区策略,仅克隆未配置表的分区策略`, cancelText: formatMessage({ id: 'odc.TaskManagePage.component.TaskTools.Cancel', defaultMessage: '取消', @@ -390,12 +363,7 @@ const ScheduleActions: React.FC = (props) => { title: `确定要删除此${scheduleTypeText}吗`, content: ( <> -
- {formatMessage({ - id: 'odc.TaskManagePage.component.TaskTools.TheTaskNeedsToBe', - defaultMessage: '任务需要重新审批,审批通过后此任务将删除', - })} -
+
作业需要重新审批,审批通过后此作业将不再显示
), cancelText: formatMessage({ @@ -408,7 +376,7 @@ const ScheduleActions: React.FC = (props) => { // setDelList?.([...delList, scheduleId]); const res = await deleteSchedule(scheduleId, schedule?.project.id); if (res?.data) { - message.success('任务需要重新审批,审批通过后此任务将删除'); + message.success('提交成功'); onReloadList?.(); onClose?.(); } diff --git a/src/component/Schedule/components/ExecutionInfoContainer/index.tsx b/src/component/Schedule/components/ExecutionInfoContainer/index.tsx index 9b47c7138..976a3ee48 100644 --- a/src/component/Schedule/components/ExecutionInfoContainer/index.tsx +++ b/src/component/Schedule/components/ExecutionInfoContainer/index.tsx @@ -16,10 +16,11 @@ interface ExecutionInfoContainer { trigger: ICycleTaskTriggerConfig; fireTimes?: number[]; type: ScheduleType; + useStyleContainer?: boolean; } const ExecutionInfoContainer: React.FC = (props) => { - const { trigger, fireTimes, type } = props; + const { trigger, fireTimes, type, useStyleContainer = true } = props; const scheduleExecStrategyMap = getScheduleExecStrategyMap(type); const isCycle = useMemo(() => { @@ -27,7 +28,7 @@ const ExecutionInfoContainer: React.FC = (props) => { }, [trigger?.triggerStrategy]); return ( - + = (props) => { const { schedule } = props; - if (!schedule.parameters.droppingTrigger) { - return null; - } const [type, setType] = useState( PartitionTypeExecutionMethod.CreatePartition, ); @@ -41,26 +38,34 @@ const PartitionPlanHeader: React.FC = (props) => { }, [type]); return ( -
- { - setType(value); - }} - className={styles.segmented} - options={[ - { - value: PartitionTypeExecutionMethod.CreatePartition, - label: '创建分区调度策略', - }, - { - value: PartitionTypeExecutionMethod.DropPartition, - label: '删除分区调度策略', - }, - ]} + + {schedule.parameters.droppingTrigger && ( + { + setType(value); + }} + className={styles.segmented} + options={[ + { + value: PartitionTypeExecutionMethod.CreatePartition, + label: '创建分区调度策略', + }, + { + value: PartitionTypeExecutionMethod.DropPartition, + label: '删除分区调度策略', + }, + ]} + /> + )} + + - -
+
); }; diff --git a/src/component/Schedule/components/OperationRecord/index.less b/src/component/Schedule/components/OperationRecord/index.less index 2856ffa67..d7e88d2d6 100644 --- a/src/component/Schedule/components/OperationRecord/index.less +++ b/src/component/Schedule/components/OperationRecord/index.less @@ -33,6 +33,16 @@ } } +.infoContainer { + background-color: var(--forbiden-color); + padding: 16px; + border: 1px solid transparent; + border-radius: 6px; + margin-bottom: 8px; + flex-direction: column; + align-items: start; +} + .nextTime:global(.ant-collapse) { :global { .ant-collapse-item > .ant-collapse-header, @@ -61,7 +71,6 @@ color: var(--icon-blue-color); } } - margin-bottom: 10px; } .infoContainer { diff --git a/src/component/Schedule/components/ScheduleDetailModal/index.less b/src/component/Schedule/components/ScheduleDetailModal/index.less index 1711dca04..3d5b1b54a 100644 --- a/src/component/Schedule/components/ScheduleDetailModal/index.less +++ b/src/component/Schedule/components/ScheduleDetailModal/index.less @@ -8,6 +8,10 @@ background: var(--odc-antd-drawer-bg-color); } +.scheduleNameTooltip { + max-width: 500px !important; +} + .content { position: absolute; top: 60px; @@ -59,6 +63,7 @@ } .detailName { + cursor: pointer; max-width: calc(100% - 100px); display: flex; .scheduleName { diff --git a/src/component/Schedule/components/ScheduleDetailModal/index.tsx b/src/component/Schedule/components/ScheduleDetailModal/index.tsx index 5698194ec..abbacaf01 100644 --- a/src/component/Schedule/components/ScheduleDetailModal/index.tsx +++ b/src/component/Schedule/components/ScheduleDetailModal/index.tsx @@ -1,4 +1,4 @@ -import { Drawer, Radio, Spin, Tag } from 'antd'; +import { Drawer, Radio, Spin, Tag, Tooltip } from 'antd'; import styles from './index.less'; import { ScheduleDetailType } from '@/d.ts/schedule'; import OperationRecord from '../OperationRecord'; @@ -81,7 +81,9 @@ const CommonTaskDetailModal: React.FC = (props)
{schedule?.scheduleName}
-
详情
+ +
详情
+
{schedule?.approvable && ( diff --git a/src/component/Schedule/components/ScheduleStatusLabel/index.tsx b/src/component/Schedule/components/ScheduleStatusLabel/index.tsx index 826da082e..9026d1426 100644 --- a/src/component/Schedule/components/ScheduleStatusLabel/index.tsx +++ b/src/component/Schedule/components/ScheduleStatusLabel/index.tsx @@ -7,15 +7,19 @@ import { LoadingOutlined, StopFilled, PauseCircleOutlined, + EllipsisOutlined, } from '@ant-design/icons'; import { Space } from 'antd'; const ScheduleStatusInfo = { [ScheduleStatus.CREATING]: { icon: ( - ), diff --git a/src/component/Schedule/components/ScheduleTable/DatabaseColumn.tsx b/src/component/Schedule/components/ScheduleTable/DatabaseColumn.tsx index 9f86c3d13..2d98bde1d 100644 --- a/src/component/Schedule/components/ScheduleTable/DatabaseColumn.tsx +++ b/src/component/Schedule/components/ScheduleTable/DatabaseColumn.tsx @@ -21,6 +21,7 @@ const DatabaseColumn: React.FC = (props) => {
{record?.attributes?.databaseInfo && ( = (props) => { )} {record?.attributes?.sourceDataBaseInfo && ( = (props) => { isSubTaskList && } {record?.attributes?.targetDataBaseInfo && ( = (props) => { { title: '作业', dataIndex: 'scheduleId', + key: 'scheduleId', width: 500, render: (id, record: IScheduleRecord) => { return ( @@ -255,6 +257,7 @@ const ScheduleTable: React.FC = (props) => { { title: '数据库', dataIndex: 'database', + key: 'database', width: 120, render: (type, record: IScheduleRecord) => { return ; @@ -265,6 +268,7 @@ const ScheduleTable: React.FC = (props) => { { title: '类型', dataIndex: 'type', + key: 'type', width: 100, render: (type) => { return <>{SchedulePageTextMap[type] || type || '-'}; @@ -278,6 +282,7 @@ const ScheduleTable: React.FC = (props) => { defaultMessage: '状态', }), dataIndex: 'status', + key: 'status', width: 150, render: (status, record: IScheduleRecord) => { return ( @@ -301,6 +306,8 @@ const ScheduleTable: React.FC = (props) => { { title: '操作', dataIndex: 'actions', + key: 'actions', + fixed: 'right' as FixedType, width: 140, render: (_, record: IScheduleRecord) => { return ( @@ -321,6 +328,7 @@ const ScheduleTable: React.FC = (props) => { const subTaskColumns = [ { dataIndex: 'id', + key: 'id', title: '执行记录ID', ellipsis: true, width: 80, @@ -340,9 +348,10 @@ const ScheduleTable: React.FC = (props) => { }, { dataIndex: 'scheduleName', + key: 'scheduleName', title: '所属作业', ellipsis: true, - width: 220, + width: 260, render: (scheduleName, record) => { return (
@@ -375,9 +384,10 @@ const ScheduleTable: React.FC = (props) => { }, { dataIndex: 'database', + key: 'database', title: '数据库', ellipsis: true, - width: 280, + width: 160, render: (database, record) => { return ; }, @@ -387,32 +397,39 @@ const ScheduleTable: React.FC = (props) => { : [ { dataIndex: 'project', + key: 'project', title: '项目', ellipsis: true, - width: 200, + width: 120, render: (project) => project?.name, }, ]), - - { - dataIndex: 'type', - title: '类型', - ellipsis: true, - width: 120, - render: (type) => SubTypeTextMap[type], - }, + ...(scheduleTabType === SchedulePageType.ALL + ? [ + { + dataIndex: 'type', + key: 'type', + title: '类型', + ellipsis: true, + width: 120, + render: (type) => SubTypeTextMap[type], + }, + ] + : []), { dataIndex: 'createTime', + key: 'createTime', title: formatMessage({ id: 'odc.component.CommonTaskDetailModal.TaskRecord.CreationTime', defaultMessage: '创建时间', }), //创建时间 ellipsis: true, - width: 180, + width: 150, render: (createTime) => getFormatDateTime(createTime), }, { dataIndex: 'status', + key: 'status', title: '状态', ellipsis: true, width: 140, @@ -422,10 +439,12 @@ const ScheduleTable: React.FC = (props) => { }, { dataIndex: 'action', + key: 'action', title: formatMessage({ id: 'odc.component.CommonTaskDetailModal.TaskRecord.Operation', defaultMessage: '操作', }), //操作 + fixed: 'right' as FixedType, ellipsis: true, width: 140, render: (_, record) => { @@ -632,7 +651,7 @@ const ScheduleTable: React.FC = (props) => { params, setParams, projectList: resProjects?.contents, - scheduleTabType: scheduleTabType, + scheduleTabType, perspective, setPerspective, subTaskParams, diff --git a/src/component/Schedule/interface.ts b/src/component/Schedule/interface.ts index d0b90c473..41da2d40c 100644 --- a/src/component/Schedule/interface.ts +++ b/src/component/Schedule/interface.ts @@ -41,7 +41,7 @@ export enum ScheduleSearchType { SCHEDULENAME = 'SCHEDULENAME', SCHEDULEID = 'SCHEDULEID', CREATOR = 'CREATOR', - // DATABASE = 'DATABASE', + DATABASE = 'DATABASE', DATASOURCE = 'DATASOURCE', CLUSTER = 'CLUSTER', TENANT = 'TENANT', diff --git a/src/component/Schedule/layout/Content.tsx b/src/component/Schedule/layout/Content.tsx index e332def0e..ecea3a992 100644 --- a/src/component/Schedule/layout/Content.tsx +++ b/src/component/Schedule/layout/Content.tsx @@ -189,6 +189,7 @@ const Content: React.FC = (props) => { creator: params.searchType === ScheduleSearchType.CREATOR ? params.searchValue : '', clusterId: params.searchType === ScheduleSearchType.CLUSTER ? params.searchValue : '', tenantId: params.searchType === ScheduleSearchType.TENANT ? params.searchValue : '', + databaseName: params.searchType === ScheduleSearchType.DATABASE ? params.searchValue : '', type: scheduleStore?.schedulePageType !== SchedulePageType.ALL ? [scheduleStore?.schedulePageType as unknown as ScheduleType] diff --git a/src/component/Schedule/layout/Header/DateSelect.tsx b/src/component/Schedule/layout/Header/DateSelect.tsx index 0c770af3e..d6bd2faba 100644 --- a/src/component/Schedule/layout/Header/DateSelect.tsx +++ b/src/component/Schedule/layout/Header/DateSelect.tsx @@ -9,8 +9,6 @@ import dayjs, { Dayjs } from 'dayjs'; const { RangePicker } = DatePicker; -export const TIME_OPTION_ALL_TASK = 'ALL'; - export const TimeOptions = [ { label: formatMessage({ id: 'odc.component.TimeSelect.LastDays', defaultMessage: '最近 7 天' }), //最近 7 天 @@ -41,7 +39,7 @@ export const TimeOptions = [ }, { label: formatMessage({ id: 'src.component.TimeSelect.9E6CA23B', defaultMessage: '全部' }), - value: TIME_OPTION_ALL_TASK, + value: 'ALL', }, { label: formatMessage({ id: 'odc.component.TimeSelect.Custom', defaultMessage: '自定义' }), //自定义 diff --git a/src/component/Schedule/layout/Header/Filter/index.tsx b/src/component/Schedule/layout/Header/Filter/index.tsx index 0f1571494..cac9c009a 100644 --- a/src/component/Schedule/layout/Header/Filter/index.tsx +++ b/src/component/Schedule/layout/Header/Filter/index.tsx @@ -57,21 +57,32 @@ const Filter: React.FC = () => { ); }; + /* 作业视角、执行视角的高亮条件 */ const isActive = useMemo(() => { - if (isScheduleView && tab !== ScheduleTab.approveByCurrentUser) { - return ( - Boolean(status.length) || - Boolean(projectIds.length) || - Boolean(type) || - Boolean(approveStatus.length) - ); - } else if (isScheduleView && tab === ScheduleTab.approveByCurrentUser) { - return Boolean(status.length) || Boolean(projectIds.length) || Boolean(type); + let _isActive = false; + if (isScheduleView) { + if (tab === ScheduleTab.approveByCurrentUser) { + _isActive = + Boolean(status.length) || + Boolean(projectIds.length) || + Boolean(type) || + timeRange !== 'ALL'; + } else { + _isActive = + Boolean(status.length) || + Boolean(projectIds.length) || + Boolean(type) || + Boolean(approveStatus.length) || + timeRange !== 'ALL'; + } } else { - return ( - Boolean(subTaskStatus.length) || Boolean(subTaskProjectIds.length) || Boolean(subTaskType) - ); + _isActive = + Boolean(subTaskStatus.length) || + Boolean(subTaskProjectIds.length) || + Boolean(subTaskType) || + timeRange !== 7; } + return _isActive; }, [ status.length, projectIds.length, @@ -81,6 +92,7 @@ const Filter: React.FC = () => { approveStatus.length, subTaskProjectIds.length, subTaskType, + timeRange, ]); const handleOpenChange = (newOpen: boolean) => { @@ -93,7 +105,7 @@ const Filter: React.FC = () => { const typeTipContent = useMemo(() => { const _type = isScheduleView ? type : subTaskType; - if (!_type) return null; + if (!_type || !isAll) return null; return (
作业类型:
@@ -102,7 +114,7 @@ const Filter: React.FC = () => {
); - }, [isScheduleView, type, subTaskType]); + }, [isScheduleView, type, subTaskType, isAll]); const projectTipContent = useMemo(() => { const _projectId = isScheduleView ? projectIds : subTaskProjectIds; diff --git a/src/component/Schedule/layout/Header/Search.tsx b/src/component/Schedule/layout/Header/Search.tsx index 6ed50e1d7..19305ab29 100644 --- a/src/component/Schedule/layout/Header/Search.tsx +++ b/src/component/Schedule/layout/Header/Search.tsx @@ -8,6 +8,7 @@ const ScheduleSearchTypeText = { [ScheduleSearchType.SCHEDULENAME]: '作业名称', [ScheduleSearchType.SCHEDULEID]: '作业ID', [ScheduleSearchType.CREATOR]: '创建人', + [ScheduleSearchType.DATABASE]: '数据库', [ScheduleSearchType.DATASOURCE]: formatMessage({ id: 'odc.component.RecordPopover.column.DataSource', defaultMessage: '数据源', diff --git a/src/component/Schedule/layout/Header/Segment.tsx b/src/component/Schedule/layout/Header/Segment.tsx index 694de8ceb..4a02ba86a 100644 --- a/src/component/Schedule/layout/Header/Segment.tsx +++ b/src/component/Schedule/layout/Header/Segment.tsx @@ -2,6 +2,7 @@ import { Segmented, Tooltip } from 'antd'; import { useContext } from 'react'; import { Perspective } from '@/component/Schedule/interface'; import ParamsContext from '@/component/Schedule/context/ParamsContext'; +import styles from './index.less'; const Segment = () => { const context = useContext(ParamsContext); @@ -13,6 +14,7 @@ const Segment = () => { setLoading(true); setPerspective(value); }} + className={styles.segmented} options={[ { value: Perspective.scheduleView, diff --git a/src/component/Schedule/layout/Header/index.less b/src/component/Schedule/layout/Header/index.less index abd8d353e..84716a0e5 100644 --- a/src/component/Schedule/layout/Header/index.less +++ b/src/component/Schedule/layout/Header/index.less @@ -29,3 +29,11 @@ } } } + +.segmented { + :global { + .ant-segmented-item-selected { + color: var(--icon-blue-color); + } + } +} diff --git a/src/component/Schedule/modals/DataArchive/Create/index.tsx b/src/component/Schedule/modals/DataArchive/Create/index.tsx index c41275edd..921772937 100644 --- a/src/component/Schedule/modals/DataArchive/Create/index.tsx +++ b/src/component/Schedule/modals/DataArchive/Create/index.tsx @@ -45,6 +45,7 @@ import { openSchedulesPage } from '@/store/helper/page'; import { getDataSourceModeConfig } from '@/common/datasource'; import { ConnectTypeText } from '@/constant/label'; import SchduleExecutionMethodForm from '@/component/Schedule/components/SchduleExecutionMethodForm'; +import { getInitScheduleName } from '@/component/Task/component/CreateTaskConfirmModal/helper'; export enum IArchiveRange { PORTION = 'portion', @@ -209,7 +210,7 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) dayOfMonth: days, dayOfWeek: days, }; - setCrontab(crontab); + crontabRef.current?.setValue(crontab); } if (triggerStrategy === TaskExecStrategy.START_AT) { formData.startAt = dayjs(startAt); @@ -743,7 +744,7 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) = ({ scheduleStore, projectId, pageStore, mode }) dayOfMonth: days, dayOfWeek: days, }; - setCrontab(crontab); + crontabRef?.current?.setValue(crontab); } if (triggerStrategy === TaskExecStrategy.START_AT) { formData.startAt = dayjs(startAt); @@ -607,7 +608,7 @@ const Create: React.FC = ({ scheduleStore, projectId, pageStore, mode }) = ({ projectId, scheduleStore, pageStore, mode }) dayOfMonth: days, dayOfWeek: days, }; - setCrontab(crontab); + crontabRef?.current?.setValue(crontab); } if (triggerStrategy === TaskExecStrategy.START_AT) { formData.startAt = dayjs(startAt) as any; @@ -931,7 +932,7 @@ const Create: React.FC = ({ projectId, scheduleStore, pageStore, mode })
= ({ scheduleStore, pageStore, projectId, theme, dayOfMonth: days, dayOfWeek: days, }; - setCrontab(crontab); + crontabRef?.current?.setValue(crontab); } if (triggerStrategy === TaskExecStrategy.START_AT) { formData.startAt = dayjs(startAt); @@ -304,15 +303,7 @@ const Create: React.FC = ({ scheduleStore, pageStore, projectId, theme, /*编辑 SQL 计划*/ }
-
- { - formatMessage({ - id: 'odc.components.CreateSQLPlanTaskModal.TheTaskNeedsToBe', - defaultMessage: '任务需要重新审批,审批通过后此任务将重新执行', - }) - /*任务需要重新审批,审批通过后此任务将重新执行*/ - } -
+
作业需要重新审批,审批通过后此作业将自动启用
), @@ -825,7 +816,7 @@ const Create: React.FC = ({ scheduleStore, pageStore, projectId, theme,
{ return `[${database?.environment?.name}]${database?.name}_${+new Date()}`; }; + +export const getInitScheduleName = (scheduleName: string, type: 'RETRY' | 'EDIT') => { + if (scheduleName) { + if (type === 'RETRY') { + return `[克隆]${scheduleName}`; + } + return scheduleName; + } + return undefined; +}; diff --git a/src/component/Task/component/ExecuteFailTip/index.tsx b/src/component/Task/component/ExecuteFailTip/index.tsx index 806bc7b51..2da43418f 100644 --- a/src/component/Task/component/ExecuteFailTip/index.tsx +++ b/src/component/Task/component/ExecuteFailTip/index.tsx @@ -4,11 +4,9 @@ import { Alert } from 'antd'; export default function ExecuteFailTip() { return ( = (props) => { }), //'分区表' key: 'tableName', dataIndex: 'tableName', - width: 164, ellipsis: true, filterDropdown: (props) => { return ( @@ -164,6 +163,7 @@ const PartitionPolicyTable: React.FC = (props) => { key: 'containsStrategy', dataIndex: 'containsStrategy', ellipsis: true, + width: 180, render: (_, record) => { const label = getStrategyLabelByConfig(record); return ( diff --git a/src/component/Task/component/SQLPreviewModal/index.tsx b/src/component/Task/component/SQLPreviewModal/index.tsx index 41e53a1a6..087b6f63a 100644 --- a/src/component/Task/component/SQLPreviewModal/index.tsx +++ b/src/component/Task/component/SQLPreviewModal/index.tsx @@ -97,7 +97,16 @@ function SQLPreviewModal(props: {
{ + if (!value || value.trim() === '') { + return Promise.reject(new Error('请输入作业名称')); + } + return Promise.resolve(); + }, + }, + ]} name={'Name'} label={'作业名称'} > diff --git a/src/component/Task/component/TaskDetailModal/TaskProgress/MultipAsyncExecute/index.tsx b/src/component/Task/component/TaskDetailModal/TaskProgress/MultipAsyncExecute/index.tsx index 06ca514e2..344656db8 100644 --- a/src/component/Task/component/TaskDetailModal/TaskProgress/MultipAsyncExecute/index.tsx +++ b/src/component/Task/component/TaskDetailModal/TaskProgress/MultipAsyncExecute/index.tsx @@ -299,7 +299,9 @@ const MultipAsyncExecute: React.FC = (props) => { EXECUTING = 0, } = multipAsyncExecuteRecordRes?.stats?.statusCount?.count ?? {}; return ( -
{`以下 ${WAIT_FOR_EXECUTION} 个数据库待执行,${EXECUTING} 个数据库执行中, ${EXECUTION_SUCCEEDED} 个数据库执行成功,${EXECUTION_FAILED}个数据库执行失败`}
+
{`以下 ${WAIT_FOR_EXECUTION} 个数据库待执行,${EXECUTING} 个数据库执行中, ${EXECUTION_SUCCEEDED} 个数据库执行成功,${EXECUTION_FAILED}个数据库执行失败`}
); }, [multipAsyncExecuteRecordRes?.stats]); diff --git a/src/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressHeader.tsx b/src/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressHeader.tsx index 6bcbeb5ab..92b0e94fa 100644 --- a/src/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressHeader.tsx +++ b/src/component/Task/component/TaskDetailModal/TaskProgress/TaskProgressHeader.tsx @@ -23,7 +23,7 @@ const TaskProgressHeader: React.FC<{ } }); return ( -
+
{formatMessage( { id: 'src.component.Task.component.CommonDetailModal.TaskProgress.4F56B34E', @@ -36,7 +36,7 @@ const TaskProgressHeader: React.FC<{ ); } return ( -
+
{formatMessage( { id: 'src.component.Task.component.CommonDetailModal.E75BF608', diff --git a/src/component/Task/component/TaskTable/index.tsx b/src/component/Task/component/TaskTable/index.tsx index 821ab72dd..6a72dc0c5 100644 --- a/src/component/Task/component/TaskTable/index.tsx +++ b/src/component/Task/component/TaskTable/index.tsx @@ -358,6 +358,7 @@ const TaskTable: React.FC = (props) => { setParams, projectList: resProjects?.contents, mode, + taskTabType, reload: () => { setLoading(true); loadData(params, pagination); diff --git a/src/component/Task/context/ParamsContext.tsx b/src/component/Task/context/ParamsContext.tsx index 89bbdb0f1..07d05cc4b 100644 --- a/src/component/Task/context/ParamsContext.tsx +++ b/src/component/Task/context/ParamsContext.tsx @@ -3,6 +3,7 @@ import { IProject } from '@/d.ts/project'; import { ITaskParam, TaskPageMode, TaskTab } from '@/component/Task/interface'; import { IScheduleParam } from '@/component/Schedule/interface'; import { SchedulePageType } from '@/d.ts/schedule'; +import { TaskPageType } from '@/d.ts'; export const defaultParam: ITaskParam = { searchValue: undefined, @@ -23,8 +24,8 @@ interface IParamsContext { ) => void; projectList: IProject[]; reload?: () => void; - scheduleTabType?: SchedulePageType; mode?: TaskPageMode; + taskTabType?: TaskPageType; } const ParamsContext: React.Context = React.createContext({ diff --git a/src/component/Task/layout/Header/DateSelect.tsx b/src/component/Task/layout/Header/DateSelect.tsx index 1cd551695..50f5d620a 100644 --- a/src/component/Task/layout/Header/DateSelect.tsx +++ b/src/component/Task/layout/Header/DateSelect.tsx @@ -9,8 +9,6 @@ import dayjs, { Dayjs } from 'dayjs'; const { RangePicker } = DatePicker; -export const TIME_OPTION_ALL_TASK = 'ALL'; - export const TimeOptions = [ { label: formatMessage({ id: 'odc.component.TimeSelect.LastDays', defaultMessage: '最近 7 天' }), //最近 7 天 @@ -41,7 +39,7 @@ export const TimeOptions = [ }, { label: formatMessage({ id: 'src.component.TimeSelect.9E6CA23B', defaultMessage: '全部' }), - value: TIME_OPTION_ALL_TASK, + value: 'ALL', }, { label: formatMessage({ id: 'odc.component.TimeSelect.Custom', defaultMessage: '自定义' }), //自定义 diff --git a/src/component/Task/layout/Header/Filter/index.tsx b/src/component/Task/layout/Header/Filter/index.tsx index e9dfe5d3a..6dd2e57a6 100644 --- a/src/component/Task/layout/Header/Filter/index.tsx +++ b/src/component/Task/layout/Header/Filter/index.tsx @@ -12,20 +12,22 @@ import styles from '../index.less'; import { TaskPageTextMap } from '@/constant/task'; import { status } from '@/component/Task/component/Status'; import { TimeOptions } from '../DateSelect'; +import { TaskPageType } from '@/d.ts'; interface IProps {} const Filter: React.FC = () => { const context = useContext(ParamsContext); - const { params, setParams, projectList, mode } = context; + const { params, setParams, projectList, mode, taskTabType } = context; const { taskStatus, projectId, taskTypes, timeRange, executeDate, tab } = params as ITaskParam; const [open, setOpen] = useState(false); const [hover, setHover] = useState(false); + const isAll = taskTabType === TaskPageType.ALL; const filterContent = () => { return (
- + {isAll && } {tab === TaskTab.executionByCurrentUser ? '' : } {mode !== TaskPageMode.PROJECT ? : null}
创建时间范围
@@ -47,7 +49,7 @@ const Filter: React.FC = () => { }; const typeTipContent = useMemo(() => { - if (!taskTypes.length) return null; + if (!taskTypes.length || !isAll) return null; return (
工单类型:
@@ -63,7 +65,7 @@ const Filter: React.FC = () => {
); - }, [taskTypes]); + }, [taskTypes, isAll]); const statusTipContent = useMemo(() => { if (!taskStatus.length) return null; diff --git a/src/component/Task/modals/LogicDatabaseAsyncTask/CreateModal/index.tsx b/src/component/Task/modals/LogicDatabaseAsyncTask/CreateModal/index.tsx index 90401e8f3..0bd56d376 100644 --- a/src/component/Task/modals/LogicDatabaseAsyncTask/CreateModal/index.tsx +++ b/src/component/Task/modals/LogicDatabaseAsyncTask/CreateModal/index.tsx @@ -80,11 +80,11 @@ const CreateModal: React.FC = (props) => { const [initialSQL, setInitialSQL] = useState(); const isInitSqlContent = useMemo(() => { - if (logicDatabaseInfo?.taskId) { + if (logicDatabaseInfo?.taskId || logicDatabaseInfo?.ddl) { return !!initialSQL; } return true; - }, [logicDatabaseInfo?.taskId, initialSQL]); + }, [logicDatabaseInfo?.taskId, logicDatabaseInfo?.ddl, initialSQL]); const loadEditData = async (taskId) => { const dataRes = (await getTaskDetail(taskId)) as TaskDetail; @@ -111,6 +111,7 @@ const CreateModal: React.FC = (props) => { if (logicDatabaseInfo?.ddl) { form?.setFieldValue('sqlContent', logicDatabaseInfo?.ddl); form?.setFieldValue('databaseId', logicDatabaseInfo?.databaseId); + setInitialSQL(logicDatabaseInfo?.ddl); } }, [logicDatabaseInfo?.ddl]); @@ -126,6 +127,7 @@ const CreateModal: React.FC = (props) => { const hadleReset = () => { form.resetFields(null); + setInitialSQL(undefined); setSqlContentType(SQLContentType.TEXT); setHasEdit(false); }; diff --git a/src/constant/schedule.ts b/src/constant/schedule.ts index 85e1d9e5e..05ec43d3d 100644 --- a/src/constant/schedule.ts +++ b/src/constant/schedule.ts @@ -17,10 +17,7 @@ export const SchedulePageTextMap = { id: 'odc.component.Task.helper.DataCleansing', defaultMessage: '数据清理', }), - [SchedulePageType.SQL_PLAN]: formatMessage({ - id: 'odc.TaskManagePage.component.helper.SqlPlan', - defaultMessage: 'SQL 计划', - }), + [SchedulePageType.SQL_PLAN]: ' SQL 计划', [SchedulePageType.PARTITION_PLAN]: formatMessage({ id: 'odc.TaskManagePage.component.TaskTable.PartitionPlan', defaultMessage: '分区计划', diff --git a/src/constant/task.ts b/src/constant/task.ts index 9db85c570..c2623caa7 100644 --- a/src/constant/task.ts +++ b/src/constant/task.ts @@ -75,7 +75,7 @@ export const TaskActionsTextMap = { [TaskActionsEnum.STOP]: '终止', [TaskActionsEnum.ROLLBACK]: '回滚', [TaskActionsEnum.EXECUTE]: '执行', - [TaskActionsEnum.PASS]: '通过', + [TaskActionsEnum.PASS]: '同意', [TaskActionsEnum.AGAIN]: '重试', [TaskActionsEnum.DOWNLOAD]: '下载', [TaskActionsEnum.REJECT]: '拒绝', diff --git a/src/d.ts/schedule.ts b/src/d.ts/schedule.ts index 32169deb4..03b9912df 100644 --- a/src/d.ts/schedule.ts +++ b/src/d.ts/schedule.ts @@ -13,10 +13,10 @@ export enum ScheduleType { DATA_ARCHIVE = 'DATA_ARCHIVE', /** 数据清理 */ DATA_DELETE = 'DATA_DELETE', - /** 分区计划 */ - PARTITION_PLAN = 'PARTITION_PLAN', /** sql 计划 */ SQL_PLAN = 'SQL_PLAN', + /** 分区计划 */ + PARTITION_PLAN = 'PARTITION_PLAN', } /** diff --git a/src/page/Project/User/ManageModal/Database/index.tsx b/src/page/Project/User/ManageModal/Database/index.tsx index b0cc32d49..e2c00e749 100644 --- a/src/page/Project/User/ManageModal/Database/index.tsx +++ b/src/page/Project/User/ManageModal/Database/index.tsx @@ -105,6 +105,9 @@ const ManageModal: React.FC = (props) => { page: current, size: pageSize, }; + if (pageSize === 0) { + return; + } // sorter params.sort = column ? `${column.dataIndex},${order === 'ascend' ? 'asc' : 'desc'}` : undefined; const res = await getDatabasePermissions(params); @@ -171,12 +174,6 @@ const ManageModal: React.FC = (props) => { }); }; - useEffect(() => { - if (projectId && userId) { - loadData(); - } - }, [userId, projectId, authorizationType]); - const handleSwitchUserTab = () => { if (authorizationType === PermissionSourceType.USER_AUTHORIZATION) { handleReload(); From 280728e043ead92494dd4bd2c74292f6efd1dab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Mon, 15 Sep 2025 17:57:30 +0800 Subject: [PATCH 056/239] =?UTF-8?q?PullRequest:=201001=20feat:=20=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=90=8E=E7=AB=AF=E6=95=B0=E6=8D=AE=E5=BA=93=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=8E=A5=E5=8F=A3=E5=AD=97=E6=AE=B5=E5=8F=98=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/listDatabaseParamsChange of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/1001 Reviewed-by: 晓康 * feat: 适配数据库列表接口变更 * fix: 恢复解决冲突刷掉的代码 --- src/common/network/database.ts | 9 +-- src/constant/database.ts | 21 ++++++ src/d.ts/database.ts | 7 ++ src/page/Project/Database/Header/Search.tsx | 71 ++++++------------ src/page/Project/Database/ParamContext.tsx | 6 +- src/page/Project/Database/hooks/useData.ts | 12 ++- .../SessionDropdown/components/Search.tsx | 74 ++++++------------- .../SessionSelect/SessionDropdown/index.tsx | 29 +++++--- src/page/Workspace/context/WorkspaceStore.tsx | 4 +- 9 files changed, 108 insertions(+), 125 deletions(-) create mode 100644 src/constant/database.ts diff --git a/src/common/network/database.ts b/src/common/network/database.ts index c2b3f07da..a8b290246 100644 --- a/src/common/network/database.ts +++ b/src/common/network/database.ts @@ -21,13 +21,13 @@ import notification from '@/util/notification'; import request from '@/util/request'; import { getDropSQL } from '@/util/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( diff --git a/src/constant/database.ts b/src/constant/database.ts new file mode 100644 index 000000000..363c95026 --- /dev/null +++ b/src/constant/database.ts @@ -0,0 +1,21 @@ +import { formatMessage } from '@/util/intl'; +import { DatabaseSearchType } from '@/d.ts/database'; + +export const DatabaseSearchTypeText = { + [DatabaseSearchType.SCHEMA_NAME]: formatMessage({ + id: 'src.component.ODCSetting.config.9EC92943', + defaultMessage: '数据库', + }), //'数据库' + [DatabaseSearchType.DATASOURCE_NAME]: formatMessage({ + id: 'odc.component.RecordPopover.column.DataSource', + defaultMessage: '数据源', + }), //数据源 + [DatabaseSearchType.CLUSTER_NAME]: formatMessage({ + id: 'odc.Connecion.ConnectionList.ParamContext.Cluster', + defaultMessage: '集群', + }), //集群 + [DatabaseSearchType.TENANT_NAME]: formatMessage({ + id: 'odc.Connecion.ConnectionList.ParamContext.Tenant', + defaultMessage: '租户', + }), //租户 +}; diff --git a/src/d.ts/database.ts b/src/d.ts/database.ts index 82014d08b..3f9e0be60 100644 --- a/src/d.ts/database.ts +++ b/src/d.ts/database.ts @@ -151,3 +151,10 @@ export enum DatabaseGroup { /** 按租户 */ tenant = 'Tenant', } + +export enum DatabaseSearchType { + SCHEMA_NAME = 'SCHEMA_NAME', + DATASOURCE_NAME = 'DATASOURCE_NAME', + CLUSTER_NAME = 'CLUSTER_NAME', + TENANT_NAME = 'TENANT_NAME', +} diff --git a/src/page/Project/Database/Header/Search.tsx b/src/page/Project/Database/Header/Search.tsx index 1796f52b2..15b38a143 100644 --- a/src/page/Project/Database/Header/Search.tsx +++ b/src/page/Project/Database/Header/Search.tsx @@ -5,34 +5,11 @@ import { formatMessage } from '@/util/intl'; import type { BaseSelectRef } from 'rc-select'; import FilterIcon from '@/component/Button/FIlterIcon'; import ParamContext from '../ParamContext'; +import { DatabaseSearchType } from '@/d.ts/database'; +import { DatabaseSearchTypeText } from '@/constant/database'; interface IProps {} -export enum SearchType { - DATABASE = 'DATABASE', - DATASOURCE = 'DATASOURCE', - CLUSTER = 'CLUSTER', - TENANT = 'TENANT', -} - -export const SearchTypeText = { - [SearchType.DATABASE]: formatMessage({ - id: 'src.component.ODCSetting.config.9EC92943', - defaultMessage: '数据库', - }), //'数据库' - [SearchType.DATASOURCE]: formatMessage({ - id: 'odc.component.RecordPopover.column.DataSource', - defaultMessage: '数据源', - }), //数据源 - [SearchType.CLUSTER]: formatMessage({ - id: 'odc.Connecion.ConnectionList.ParamContext.Cluster', - defaultMessage: '集群', - }), //集群 - [SearchType.TENANT]: formatMessage({ - id: 'odc.Connecion.ConnectionList.ParamContext.Tenant', - defaultMessage: '租户', - }), //租户 -}; const splitKey = '_$$$odc$$$_'; const RemoveSplitInput = forwardRef(function RemoveSplitInput({ value, ...rest }: any, ref) { @@ -53,7 +30,7 @@ const RemoveSplitInput = forwardRef(function RemoveSplitInput({ value, ...rest } prefix={} suffix={ - {SearchTypeText[type]} + {DatabaseSearchTypeText[type]} } {...rest} @@ -77,32 +54,28 @@ const Search: React.FC = function () { return; } setOptions( - [SearchType.DATABASE, SearchType.DATASOURCE, SearchType.CLUSTER, SearchType.TENANT]?.map( - (v) => { - return { - value: value + splitKey + v, - label: ( + Object.values(DatabaseSearchType)?.map((v) => { + return { + value: value + splitKey + v, + label: ( +
-
- {value} -
-
- {SearchTypeText[v]} -
+ {value} +
+
+ {DatabaseSearchTypeText[v]}
- ), - }; - }, - ), +
+ ), + }; + }), ); return; } diff --git a/src/page/Project/Database/ParamContext.tsx b/src/page/Project/Database/ParamContext.tsx index 7de3c0197..d11b92e8c 100644 --- a/src/page/Project/Database/ParamContext.tsx +++ b/src/page/Project/Database/ParamContext.tsx @@ -3,7 +3,7 @@ import { DBType } from '@/d.ts/database'; import React from 'react'; import { IEnvironment } from '@/d.ts/environment'; import { DatabaseGroup } from '@/d.ts/database'; -import { SearchType } from './Header/Search'; +import { DatabaseSearchType } from '@/d.ts/database'; export interface IFilterParams { environmentId: number[]; connectType: ConnectType[]; @@ -11,8 +11,8 @@ export interface IFilterParams { } interface IParamContext { - searchValue: { value: string; type: SearchType }; - setSearchvalue?: (v: string, type: SearchType) => void; + searchValue: { value: string; type: DatabaseSearchType }; + setSearchvalue?: (v: string, type: DatabaseSearchType) => void; filterParams?: IFilterParams; setFilterParams?: (params: IFilterParams) => void; reload?: () => void; diff --git a/src/page/Project/Database/hooks/useData.ts b/src/page/Project/Database/hooks/useData.ts index 3d35bccce..8bdc67359 100644 --- a/src/page/Project/Database/hooks/useData.ts +++ b/src/page/Project/Database/hooks/useData.ts @@ -1,7 +1,7 @@ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { IDatabase } from '@/d.ts/database'; import { listDatabases } from '@/common/network/database'; -import { SearchType } from '../Header/Search'; +import { DatabaseSearchType } from '@/d.ts/database'; import { IFilterParams } from '../ParamContext'; import ProjectContext from '../../ProjectContext'; import datasourceStatus from '@/store/datasourceStatus'; @@ -35,9 +35,9 @@ const useData = (id) => { const [openObjectStorage, setOpenObjectStorage] = useState(false); const [openManageLogicDatabase, setOpenManageLogicDatabase] = useState(false); const [database, setDatabase] = useState(null); - const [searchValue, setSearchValue] = useState<{ value: string; type: SearchType }>({ + const [searchValue, setSearchValue] = useState<{ value: string; type: DatabaseSearchType }>({ value: null, - type: SearchType.DATABASE, + type: DatabaseSearchType.SCHEMA_NAME, }); const { loading, run: fetchDatabases } = useRequest(listDatabases, { @@ -55,15 +55,13 @@ const useData = (id) => { projectId: parseInt(id), page: 1, size: 99999, - name: searchValue?.value, + fuzzyKeyword: searchValue?.value, environmentId: environmentId, includesPermittedAction: true, includesDbOwner: true, type, connectType, - dataSourceName: searchValue?.type === SearchType.DATASOURCE ? searchValue?.value : null, - clusterName: searchValue?.type === SearchType.CLUSTER ? searchValue?.value : null, - tenantName: searchValue?.type === SearchType.TENANT ? searchValue?.value : null, + searchType: searchValue?.type, }); if (res) { datasourceStatus.asyncUpdateStatus( diff --git a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/components/Search.tsx b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/components/Search.tsx index 5ec86fd05..bcd774ed0 100644 --- a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/components/Search.tsx +++ b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/components/Search.tsx @@ -5,38 +5,16 @@ import { formatMessage } from '@/util/intl'; import { SearchOutlined } from '@ant-design/icons'; import styles from '../index.less'; import SessionContext from '@/page/Workspace/components/SessionContextWrap/context'; +import { DatabaseSearchType } from '@/d.ts/database'; +import { DatabaseSearchTypeText } from '@/constant/database'; interface IProps { - searchValue: { value: string; type: SearchType }; - setSearchvalue: (v: string, type: SearchType) => void; + searchValue: { value: string; type: DatabaseSearchType }; + setSearchvalue: (v: string, type: DatabaseSearchType) => void; searchValueByDataSource: string; setSearchValueByDataSource: React.Dispatch>; } -export enum SearchType { - DATABASE = 'DATABASE', - DATASOURCE = 'DATASOURCE', - CLUSTER = 'CLUSTER', - TENANT = 'TENANT', -} -export const SearchTypeText = { - [SearchType.DATABASE]: formatMessage({ - id: 'src.component.ODCSetting.config.9EC92943', - defaultMessage: '数据库', - }), //'数据库' - [SearchType.DATASOURCE]: formatMessage({ - id: 'odc.component.RecordPopover.column.DataSource', - defaultMessage: '数据源', - }), //数据源 - [SearchType.CLUSTER]: formatMessage({ - id: 'odc.Connecion.ConnectionList.ParamContext.Cluster', - defaultMessage: '集群', - }), //集群 - [SearchType.TENANT]: formatMessage({ - id: 'odc.Connecion.ConnectionList.ParamContext.Tenant', - defaultMessage: '租户', - }), //租户 -}; const splitKey = '_$$$odc$$$_'; const RemoveSplitInput = forwardRef(function RemoveSplitInput({ value, ...rest }: any, ref) { @@ -53,7 +31,7 @@ const RemoveSplitInput = forwardRef(function RemoveSplitInput({ value, ...rest } prefix={} suffix={ - {SearchTypeText[type]} + {DatabaseSearchTypeText[type]} } {...rest} @@ -87,32 +65,28 @@ const Search: React.FC = function (props) { return; } setOptions( - [SearchType.DATABASE, SearchType.DATASOURCE, SearchType.CLUSTER, SearchType.TENANT]?.map( - (v) => { - return { - value: value + splitKey + v, - label: ( + Object.values(DatabaseSearchType)?.map((v) => { + return { + value: value + splitKey + v, + label: ( +
-
- {value} -
-
- {SearchTypeText[v]} -
+ {value} +
+
+ {DatabaseSearchTypeText[v]}
- ), - }; - }, - ), +
+ ), + }; + }), ); return; } diff --git a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/index.tsx b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/index.tsx index d3dd798dd..3d8c72775 100644 --- a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/index.tsx +++ b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SessionDropdown/index.tsx @@ -1,6 +1,6 @@ import { getDataSourceModeConfig } from '@/common/datasource'; import { IDataSourceModeConfig } from '@/common/datasource/interface'; -import { listDatabases } from '@/common/network/database'; +import { listDatabases, listDatabasesParams } from '@/common/network/database'; import ConnectionPopover from '@/component/ConnectionPopover'; import { ConnectionMode, TaskType, IDatabaseHistoriesParam } from '@/d.ts'; import { IDatabase } from '@/d.ts/database'; @@ -19,7 +19,7 @@ import React, { Key, useContext, useEffect, useMemo, useRef, useState } from 're import SessionContext from '../../context'; import { DEFALT_HEIGHT, DEFALT_WIDTH } from '../const'; import styles from './index.less'; -import Search, { SearchType } from './components/Search'; +import Search from './components/Search'; import Group from '@/page/Workspace/SideBar/ResourceTree/DatabaseGroup'; import { DatabaseGroup } from '@/d.ts/database'; import useGroupData from '@/page/Workspace/SideBar/ResourceTree/DatabaseTree/useGroupData'; @@ -27,6 +27,7 @@ import { SelectItemProps } from '@/page/Project/Sensitive/interface'; import DatabaseSelectTab from './components/Tab'; import { GroupNodeTitle } from './components/DatabasesTitle'; import RecentlyDatabaseEmpty from '@/component/Empty/RecentlyDatabaseEmpty'; +import { DatabaseSearchType } from '@/d.ts/database'; import { getDatabasesHistories } from '@/common/network/task'; import { NodeType, @@ -121,7 +122,7 @@ const SessionDropdown: React.FC = (props) => { const { datasourceId } = useParams<{ datasourceId: string; }>(); - const [searchValue, setSearchValue] = useState<{ value: string; type: SearchType }>({ + const [searchValue, setSearchValue] = useState<{ value: string; type: DatabaseSearchType }>({ value: null, type: null, }); @@ -175,7 +176,19 @@ const SessionDropdown: React.FC = (props) => { if (!support) { return false; } - if (!taskType && !getDataSourceModeConfig(database?.dataSource?.type)?.features?.sqlconsole) { + if ( + scheduleType && + !getDataSourceModeConfig(database?.dataSource?.type)?.features?.schedule?.includes( + scheduleType, + ) + ) { + return false; + } + if ( + !taskType && + !scheduleType && + !getDataSourceModeConfig(database?.dataSource?.type)?.features?.sqlconsole + ) { return false; } if ( @@ -206,18 +219,16 @@ const SessionDropdown: React.FC = (props) => { useEffect(() => { if (isOpen) { - const params = { + const params: listDatabasesParams = { projectId, dataSourceId: datasourceId ? toInteger(datasourceId) : dataSourceId, page: 1, size: 99999, - name: searchValue?.value, + fuzzyKeyword: searchValue?.value, containsUnassigned: userStore.isPrivateSpace(), existed: true, includesPermittedAction: true, - dataSourceName: searchValue?.type === SearchType.DATASOURCE ? searchValue?.value : null, - clusterName: searchValue?.type === SearchType.CLUSTER ? searchValue?.value : null, - tenantName: searchValue?.type === SearchType.TENANT ? searchValue?.value : null, + searchType: searchValue?.type, }; // 个人空间不需要获取数据库的权限 if (userStore?.isPrivateSpace()) { diff --git a/src/page/Workspace/context/WorkspaceStore.tsx b/src/page/Workspace/context/WorkspaceStore.tsx index 6749c28c8..134a14ea1 100644 --- a/src/page/Workspace/context/WorkspaceStore.tsx +++ b/src/page/Workspace/context/WorkspaceStore.tsx @@ -28,7 +28,7 @@ import { listProjects } from '@/common/network/project'; import { useParams } from '@umijs/max'; import { toInteger } from 'lodash'; import datasourceStatus from '@/store/datasourceStatus'; -import { listDatabases } from '@/common/network/database'; +import { listDatabases, listDatabasesParams } from '@/common/network/database'; import { IDatabase } from '@/d.ts/database'; import { DBObjectSyncStatus, DatabaseGroup } from '@/d.ts/database'; import { ResourceNodeType } from '@/page/Workspace/SideBar/ResourceTree/type'; @@ -106,7 +106,7 @@ export default function WorkspaceStore({ children }) { }, []); const reloadDatabaseList = useCallback(async () => { - const params = { + const params: listDatabasesParams = { page: 1, size: 99999, containsUnassigned: true, From 04354dfdc010bfeb54c5d518efbf18970bdea8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B9=8B=E7=91=9B?= Date: Tue, 16 Sep 2025 15:03:18 +0800 Subject: [PATCH 057/239] PullRequest: 1005 feat/UDF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/UDF of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.4.1 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/1005 Reviewed-by: 晓康 * feat: 添加函数类型 tab * feat: 外部自定义函数表单项添加 * feat: 设置函数类型默认值 * feat: 资源树上新加外部资源节点 * fix: 移除多余icon * feat: 外部资源节点操作 * feat: 新建资源弹窗 * feat: 调整新建外部资源弹窗的可选提示 * refactor: 使用 antd5 新参数 * feat: 类型用 autoComplete * feat: 新建函数弹窗样式调整 * feat: 上次文件组件样式调整 * feat: 完善创建外部资源弹窗校验 * feat: 文件上传组件 * refactor: 删除多余文件 * feat: 调整 fileUpload 组件样式 * refactor: 优化代码 * feat: 移除多余 state * feat: loading 使用灰色 ob icon * feat: 保存 support * feat: 按session 数据判断外部函数tab是否展示 * refactor: 删除不存在的国际化token * feat: 点击新建函数中资源下拉框对应的新建资源,打开响应弹窗 * refactor: 删除调试的代码 * feat: file 使用binary文件传递 * feat: 新建外部资源接口调整 * feat: 补充type参数 * feat: 补充参数 * feat: 设置req的type * feat: 外部资源列表展示 * feat: 下载与删除 * feat: 补充菜单操作 * feat: 菜单功能补充新建外部自定义函数和刷新 * fix: 补充数值校验 * feat: 基本信息 * feat: 内容tab * feat: 优化接口写法 * refactor: 删除多余token * feat: 资源来源交互逻辑 * refactor: 删除多余传参 * feat: 基本信息展示 * feat: 类型选项 * fix: 修正外部函数表单的类型输入 * feat: showTip 逻辑调整 * feat: 创建外部函数参数调整 * feat: 外部资源相关接口改为 sid:sessionId 格式 * feat: 外部资源节点展示 * feat: 删除补充type 参数 * feat: 下载功能完善 * feat: 外部资源全局搜索 * refactor: 删除多余国际化token * fix: 修复拼写问题 * fix: 修复拼写问题 * feat: 优化上传接口 * refactor: 优化上传 * feat: 外部资源节点icon * fix: 调整操作顺序 * feat: 优化外部资源展示页 * fix: 外部资源查看属性 * fix: 外部函数展示属性补全 * feat: 外部资源tab样式 * fix: 优化外部资源查看页 * fix: 资源页icon * fix: key 调整 * feat: 添加资源树刷新 * fix: key 调整 * refactor: 优化外部资源列表获取 * feat: 不支持展示创建时间 * feat: 新建函数弹窗参数修改 * fix: 资源来源默认值设置 * feat: 样式调整 * feat: 样式调整 * fix: 接口补充sid * feat: 接口提取 * refactor: 优化 TS 定义 * refactor: 删除多余代码 * feat: background color token * fix: upload color * refactor: 移除多余代码 * refactor: 移除多余代码,图表库禁用使用token * feat: border color 使用token * feat: removeButton color 使用token * feat: border color 使用 token * feat: 资源icon使用grey7 * refactor: 删除多余修改 --- src/common/network/externalResource.ts | 149 ++++++ src/common/network/index.ts | 1 + .../CreateExternalResourceModal/index.tsx | 272 +++++++++++ src/component/CreateFunctionModal/index.less | 21 + src/component/CreateFunctionModal/index.tsx | 454 ++++++++++++++---- src/component/FileUpload/index.less | 86 ++++ src/component/FileUpload/index.tsx | 287 +++++++++++ .../OrganizationSelectModal/index.less | 2 +- src/component/WindowManager/config.tsx | 10 +- src/component/WindowManager/index.tsx | 8 +- src/d.ts/externalResoruce.ts | 17 + src/d.ts/index.ts | 20 + .../Auth/Role/component/FormModal/index.less | 2 +- src/page/Workspace/GlobalModals/index.tsx | 2 + .../DatabaseSearchModal/constant.ts | 10 + .../SideBar/ResourceTree/Nodes/database.tsx | 5 + .../ResourceTree/Nodes/externalResource.tsx | 79 +++ .../TreeNodeMenu/config/externalResource.tsx | 192 ++++++++ .../TreeNodeMenu/config/index.tsx | 2 + .../ResourceTree/TreeNodeMenu/index.tsx | 10 +- .../Workspace/SideBar/ResourceTree/const.ts | 6 + .../Workspace/SideBar/ResourceTree/helper.ts | 17 + .../Workspace/SideBar/ResourceTree/type.ts | 3 + .../ExternalResourcePage/index.less | 66 +++ .../components/ExternalResourcePage/index.tsx | 306 ++++++++++++ .../ShowFunctionBaseInfoForm/index.tsx | 38 ++ src/store/helper/page/openPage.ts | 20 + src/store/helper/page/pages/index.ts | 19 + src/store/modal.ts | 36 ++ src/store/sessionManager/database.ts | 97 +++- src/store/sessionManager/session.ts | 6 + src/svgr/greyOb.svg | 1 + src/util/utils.ts | 20 + 33 files changed, 2172 insertions(+), 92 deletions(-) create mode 100644 src/common/network/externalResource.ts create mode 100644 src/component/CreateExternalResourceModal/index.tsx create mode 100644 src/component/CreateFunctionModal/index.less create mode 100644 src/component/FileUpload/index.less create mode 100644 src/component/FileUpload/index.tsx create mode 100644 src/d.ts/externalResoruce.ts create mode 100644 src/page/Workspace/SideBar/ResourceTree/Nodes/externalResource.tsx create mode 100644 src/page/Workspace/SideBar/ResourceTree/TreeNodeMenu/config/externalResource.tsx create mode 100644 src/page/Workspace/components/ExternalResourcePage/index.less create mode 100644 src/page/Workspace/components/ExternalResourcePage/index.tsx create mode 100644 src/svgr/greyOb.svg diff --git a/src/common/network/externalResource.ts b/src/common/network/externalResource.ts new file mode 100644 index 000000000..658bc6346 --- /dev/null +++ b/src/common/network/externalResource.ts @@ -0,0 +1,149 @@ +/* + * 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 !== false; + } 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 || '创建失败'); + } +} 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/component/CreateExternalResourceModal/index.tsx b/src/component/CreateExternalResourceModal/index.tsx new file mode 100644 index 000000000..1b3ae9484 --- /dev/null +++ b/src/component/CreateExternalResourceModal/index.tsx @@ -0,0 +1,272 @@ +/* + * 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 { Modal, Form, Input, Button, message, AutoComplete, Row, Col } from 'antd'; +import { UploadFile } from 'antd/lib/upload/interface'; +import React, { useState, useEffect, useRef } from 'react'; +import { inject, observer } from 'mobx-react'; +import { ModalStore } from '@/store/modal'; +import { useDBSession } from '@/store/sessionManager/hooks'; +import { getCurrentOrganizationId } from '@/store/setting'; +import FileUpload, { FileUploadRef } from '../FileUpload'; +import { createExternalResource } from '@/common/network'; + +const { TextArea } = Input; + +interface IProps { + modalStore?: ModalStore; +} + +const CreateExternalResourceModal: React.FC = ({ modalStore }) => { + const [form] = Form.useForm(); + const [uploading, setUploading] = useState(false); + const [fileList, setFileList] = useState([]); + const [selectedFile, setSelectedFile] = useState(null); + const fileUploadRef = useRef(null); + + const { createExternalResourceModalVisible, createExternalResourceModalData } = modalStore; + const databaseId = createExternalResourceModalData?.databaseId; + const dbName = createExternalResourceModalData?.dbName; + const { session } = useDBSession(databaseId); + const organizationId = getCurrentOrganizationId(); + + // 资源类型选项 + const resourceTypeOptions = [ + { value: 'JAVA_JAR', label: 'JAVA_JAR' }, + { value: 'PYTHON_PY', label: 'PYTHON_PY' }, + ]; + + useEffect(() => { + if (createExternalResourceModalVisible) { + // 初始化表单 + form.resetFields(); + setFileList([]); + setSelectedFile(null); + } + }, [createExternalResourceModalVisible, form]); + + // 上传文件并创建外部资源 + const uploadFileAndCreateResource = async (file: File, values: any): Promise => { + if (!session) { + throw new Error('会话信息不存在'); + } + + const { sessionId } = session; + const { resourceName, resourceType, description } = values; + const databaseName = dbName || session.database?.dbName; + + // 构建 FormData + const formData = new FormData(); + formData.append('file', file); + + // 构建 req参数 + const reqData = { + schemaName: databaseName, + name: file.name, + comment: description, + type: resourceType, + }; + + const reqBlob = new Blob([JSON.stringify(reqData)], { type: 'application/json' }); + formData.append('req', reqBlob); + + await createExternalResource({ formData, sessionId, databaseName, resourceName }); + }; + + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + + if (!selectedFile) { + message.error('请选择文件'); + return; + } + + setUploading(true); + + // 上传文件并创建外部资源 + await uploadFileAndCreateResource(selectedFile, values); + if (session?.database?.getExternalResourceList) { + await session.database.getExternalResourceList(); + } + + handleCancel(); + } catch (error) { + console.error('创建外部资源失败:', error); + } finally { + setUploading(false); + } + }; + + const handleCancel = () => { + setSelectedFile(null); + modalStore.changeCreateExternalResourceModalVisible(false); + }; + + const handleFileChange = ({ fileList: newFileList }: { fileList: UploadFile[] }) => { + setFileList(newFileList.slice(-1)); // 只保留最后一个文件 + }; + + const handleFileRemove = () => { + setFileList([]); + setSelectedFile(null); + }; + + const uploadProps = { + name: 'file', + multiple: false, + maxCount: 1, + fileList, + onChange: handleFileChange, + onRemove: handleFileRemove, + beforeUpload: (file: File) => { + const isValidType = + file.name.endsWith('.jar') || file.name.endsWith('.py') || file.name.endsWith('.zip'); + if (!isValidType) { + message.error('仅支持 .jar、.py、.zip 文件'); + return false; + } + + const isLt512M = file.size / 1024 / 1024 < 512; + if (!isLt512M) { + message.error('文件大小必须小于 512MB'); + return false; + } + + return false; // 阻止自动上传,我们手动控制 + }, + showUploadList: false, + }; + + return ( + + 取消 + , + , + ]} + destroyOnHidden + > + + +
+ { + if (value.length > 128) { + callback('不超过 128 个字符'); + } + callback(); + }, + }, + ]} + > + + + + + + + option?.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1 + } + /> + + + + + { + if (value.size > 512 * 1024 * 1024) { + return Promise.reject('文件不能大于 512 MB'); + } + return Promise.resolve(); + }, + }, + ]} + > + { + setSelectedFile(file); + message.success(`文件 ${file.name} 选择成功`); + }} + onError={(file, error) => { + message.error(`文件验证失败: ${error}`); + setSelectedFile(null); + }} + onRemove={() => { + setSelectedFile(null); + }} + /> + + + +