From 5fbeef61cfa1f81f32d035223a051cff439cb7c6 Mon Sep 17 00:00:00 2001 From: actiontech-zihan Date: Fri, 19 Jun 2026 02:18:32 +0800 Subject: [PATCH] feat: add SQL remediation tracking delivery Fixes #2988 --- packages/base/src/locale/en-US/dmsMenu.ts | 1 + packages/base/src/locale/zh-CN/dmsMenu.ts | 1 + .../UserMenu/components/GlobalSetting.tsx | 176 ++++++++++++----- packages/base/src/scripts/version.ts | 2 +- .../lib/api/sqle/service/SqlManage/index.d.ts | 103 +++------- .../api/sqle/service/SqlManage/index.enum.ts | 20 ++ .../lib/api/sqle/service/SqlManage/index.ts | 56 ++++-- .../shared/lib/api/sqle/service/common.d.ts | 84 ++++++++- packages/sqle/src/data/EmitterKey.ts | 1 + .../sqle/src/locale/en-US/managementConf.ts | 13 ++ .../sqle/src/locale/en-US/sqlManagement.ts | 28 ++- .../sqle/src/locale/zh-CN/managementConf.ts | 12 ++ .../sqle/src/locale/zh-CN/sqlManagement.ts | 39 +++- .../page/SqlAnalyze/SqlAnalyze/SqlAnalyze.tsx | 33 ++-- .../src/page/SqlAnalyze/SqlAnalyze/index.tsx | 2 + .../src/page/SqlAnalyze/SqlAnalyze/style.ts | 56 +++--- .../SqlManage/RemediationCompare.tsx | 91 +++++++++ .../src/page/SqlAnalyze/SqlManage/index.tsx | 113 ++++------- .../SQLEEIndex/RemediationStatusTag.tsx | 36 ++++ .../component/SQLEEIndex/column.tsx | 28 ++- .../SQLEEIndex/hooks/useGetTableFilterInfo.ts | 22 ++- .../component/SQLEEIndex/index.tsx | 90 ++++++--- .../ScanTypeSqlCollection/index.type.ts | 3 +- .../Detail/ScanTypeSqlCollection/indx.tsx | 178 +++++++++++++++++- .../Detail/ScanTypeSqlCollection/style.ts | 58 +++++- .../page/SqlManagementConf/Detail/index.tsx | 133 +++++++++++-- .../SqlManagementRemediationReport/index.tsx | 70 +++++++ packages/sqle/src/router/config.tsx | 13 +- 28 files changed, 1128 insertions(+), 334 deletions(-) create mode 100644 packages/sqle/src/page/SqlAnalyze/SqlManage/RemediationCompare.tsx create mode 100644 packages/sqle/src/page/SqlManagement/component/SQLEEIndex/RemediationStatusTag.tsx create mode 100644 packages/sqle/src/page/SqlManagementRemediationReport/index.tsx diff --git a/packages/base/src/locale/en-US/dmsMenu.ts b/packages/base/src/locale/en-US/dmsMenu.ts index 75c3595578..40443b36a2 100644 --- a/packages/base/src/locale/en-US/dmsMenu.ts +++ b/packages/base/src/locale/en-US/dmsMenu.ts @@ -63,6 +63,7 @@ export default { title: 'Global settings', userCenter: 'User center', reportStatistics: 'Report statistics', + sqlManagementRemediationReport: 'SQL management remediation report', viewRule: 'View rule', ruleManage: 'Rule management', system: 'System settings', diff --git a/packages/base/src/locale/zh-CN/dmsMenu.ts b/packages/base/src/locale/zh-CN/dmsMenu.ts index 232d307dd4..f5a4f89abf 100644 --- a/packages/base/src/locale/zh-CN/dmsMenu.ts +++ b/packages/base/src/locale/zh-CN/dmsMenu.ts @@ -65,6 +65,7 @@ export default { title: '全局设置', userCenter: '用户中心', reportStatistics: '报表统计', + sqlManagementRemediationReport: 'SQL 管控整改报表', viewRule: '查看规则', ruleManage: '规则管理', system: '系统设置', diff --git a/packages/base/src/page/Nav/SideMenu/UserMenu/components/GlobalSetting.tsx b/packages/base/src/page/Nav/SideMenu/UserMenu/components/GlobalSetting.tsx index a8346481a8..cafb7f7c67 100644 --- a/packages/base/src/page/Nav/SideMenu/UserMenu/components/GlobalSetting.tsx +++ b/packages/base/src/page/Nav/SideMenu/UserMenu/components/GlobalSetting.tsx @@ -6,8 +6,12 @@ import { UserShieldFilled, CenterCircleHexagonFilled, DatabaseFilled, + // #if [sqle] + ProfileSquareFilled, + SignalFilled, ProfileEditFilled, - OperateAuditFilled + ManagementFilled + // #endif } from '@actiontech/icons'; import { ContextMenuItem } from './ContextMenu/index.type'; import ContextMenu from './ContextMenu'; @@ -27,53 +31,129 @@ const GlobalSetting: React.FC = () => { const { checkPagePermission } = usePermission(); - const menus: ContextMenuItem[] = useMemo(() => { - const handleClickItem = (path: string) => { - navigate(path); - }; - - const menusWithPermission: Array< - ContextMenuItem & { permission?: PermissionsConstantType } - > = [ - { - key: 'user-center', - icon: , - text: t('menu.userCenter'), - onClick: () => handleClickItem(ROUTE_PATHS.BASE.USER_CENTER), - permission: PERMISSIONS.PAGES.BASE.USER_CENTER - }, - { - key: 'data-source-management', - icon: , - text: t('dmsMenu.globalSettings.instanceManager'), - onClick: () => - handleClickItem(ROUTE_PATHS.BASE.DATA_SOURCE_MANAGEMENT.index.path), - permission: PERMISSIONS.PAGES.BASE.DATA_SOURCE_MANAGEMENT - }, - // #if [sqle] - { - key: 'rule-manager', - icon: , - text: t('dmsMenu.globalSettings.ruleManage'), - onClick: () => - handleClickItem(ROUTE_PATHS.SQLE.RULE_MANAGEMENT.index.path), - permission: PERMISSIONS.PAGES.SQLE.RULE_MANAGEMENT - }, - // #endif - { - key: 'operationRecord', - icon: , - text: t('dmsMenu.globalSettings.globalOperationRecord'), - onClick: () => - handleClickItem(ROUTE_PATHS.SQLE.GLOBAL_OPERATION_LOG.index), - permission: PERMISSIONS.PAGES.SQLE.GLOBAL_OPERATION_RECORD - }, - { - key: 'system', - icon: , - text: t('dmsMenu.globalSettings.system'), - onClick: () => handleClickItem(ROUTE_PATHS.BASE.SYSTEM.index.path), - permission: PERMISSIONS.PAGES.BASE.SYSTEM_SETTING + return ( + +
{t('dmsMenu.globalSettings.title')}
+
+ +
handleClickItem('/user-center')} + > + + + {t('dmsMenu.globalSettings.userCenter')} + +
+
+ +
handleClickItem(`/data-source-management`)} + > + + + {t('dmsMenu.globalSettings.instanceManager')} + +
+
+ + {/* #if [sqle]*/} +
handleClickItem(`/sqle/rule`)} + > + + + {t('dmsMenu.globalSettings.viewRule')} + +
+ {/* #endif */} +
+ + {/* #if [sqle] */} +
handleClickItem('/sqle/report-statistics')} + > + + + {t('dmsMenu.globalSettings.reportStatistics')} + +
+
+ handleClickItem('/sqle/sql-management-remediation-report') + } + > + + + {t('dmsMenu.globalSettings.sqlManagementRemediationReport')} + +
+
handleClickItem(`/sqle/rule`)} + > + + + {t('dmsMenu.globalSettings.viewRule')} + +
+
handleClickItem(`/sqle/rule-manager`)} + > + + + {t('dmsMenu.globalSettings.ruleManage')} + +
+ {/* #endif */} +
handleClickItem(`/system`)} + > + + + {t('dmsMenu.globalSettings.system')} + +
+
+
+ {/* todo: hide theme change in +
+ + {t('dmsMenu.globalSettings.changeTheme')} + +
+ updateTheme(SupportTheme.LIGHT)} + className={classNames('footer-icon-wrapper', { + 'footer-icon-active': theme === SupportTheme.LIGHT + })} + > + + + updateTheme(SupportTheme.DARK)} + className={classNames('footer-icon-wrapper', { + 'footer-icon-active': theme === SupportTheme.DARK + })} + > + + +
+
*/} + } ]; diff --git a/packages/base/src/scripts/version.ts b/packages/base/src/scripts/version.ts index 6d5baaff06..c94a2e835f 100644 --- a/packages/base/src/scripts/version.ts +++ b/packages/base/src/scripts/version.ts @@ -1 +1 @@ -export const UI_VERSION = 'sync/data-masking 417b194dd'; +export const UI_VERSION = 'zjrc_3.2408 a32b5993'; diff --git a/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts b/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts index 47e4da1ef4..a6b19ee5bd 100644 --- a/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts +++ b/packages/shared/lib/api/sqle/service/SqlManage/index.d.ts @@ -12,10 +12,12 @@ import { exportSqlManageV1FilterStatusEnum, exportSqlManageV1SortFieldEnum, exportSqlManageV1SortOrderEnum, + exportSqlManageRemediationV1ExportScopeEnum, GetSqlManageListV2FilterSourceEnum, GetSqlManageListV2FilterAuditLevelEnum, GetSqlManageListV2FilterStatusEnum, GetSqlManageListV2FilterPriorityEnum, + GetSqlManageListV2FilterRemediationStatusEnum, GetSqlManageListV2SortFieldEnum, GetSqlManageListV2SortOrderEnum, exportSqlManageV2FilterPriorityEnum, @@ -41,10 +43,9 @@ import { IBatchUpdateSqlManageReq, IBaseRes, IGetSqlManageRuleTipsResp, - ISqlManageCodingReq, - IPostSqlManageCodingResp, IGetSqlManageSqlAnalysisResp, - ISqlManageAnalysisChartResp + IGetSqlManageRemediationResp, + IGetSqlManageRemediationOverviewResp } from '../common.d'; export interface IGetGlobalSqlManageListParams { @@ -162,6 +163,20 @@ export interface IExportSqlManageV1Params { sort_order?: exportSqlManageV1SortOrderEnum; } +export interface IExportGlobalSqlManageRemediationV1Params {} + +export interface IExportSqlManageRemediationV1Params { + project_name: string; + + export_scope: exportSqlManageRemediationV1ExportScopeEnum; + + filter_instance_id?: string; + + instance_audit_plan_id?: number; + + audit_plan_type?: string; +} + export interface IGetSqlManageRuleTipsParams { project_name: string; } @@ -222,6 +237,8 @@ export interface IGetSqlManageListV2Params { filter_status?: GetSqlManageListV2FilterStatusEnum; + filter_remediation_status?: GetSqlManageListV2FilterRemediationStatusEnum; + filter_rule_name?: string; filter_db_type?: string; @@ -247,82 +264,22 @@ export interface IGetSqlManageListV2Params { export interface IGetSqlManageListV2Return extends IGetSqlManageListResp {} -export interface IExportSqlManageV2Params { +export interface IGetSqlManageRemediationV1Params { project_name: string; - fuzzy_search_sql_fingerprint?: string; - - filter_assignee?: string; - - filter_by_environment_tag?: string; - - filter_priority?: exportSqlManageV2FilterPriorityEnum; - - filter_instance_id?: string; - - filter_source?: exportSqlManageV2FilterSourceEnum; - - filter_audit_level?: exportSqlManageV2FilterAuditLevelEnum; - - filter_last_audit_start_time_from?: string; - - filter_last_audit_start_time_to?: string; - - filter_status?: exportSqlManageV2FilterStatusEnum; - - filter_db_type?: string; - - filter_rule_name?: string; - - fuzzy_search_endpoint?: string; - - fuzzy_search_schema_name?: string; - - sort_field?: exportSqlManageV2SortFieldEnum; - - sort_order?: exportSqlManageV2SortOrderEnum; - - export_format?: exportSqlManageV2ExportFormatEnum; + sql_manage_id: string; } -export interface IGetSqlManageListV3Params { - project_name: string; - - fuzzy_search_sql_fingerprint?: string; - - filter_assignee?: string; - - filter_instance_id?: string; - - filter_source?: GetSqlManageListV3FilterSourceEnum; - - filter_audit_level?: GetSqlManageListV3FilterAuditLevelEnum; - - filter_last_audit_start_time_from?: string; - - filter_last_audit_start_time_to?: string; +export interface IGetSqlManageRemediationV1Return + extends IGetSqlManageRemediationResp {} - filter_status?: GetSqlManageListV3FilterStatusEnum; - - filter_rule_name?: string; - - filter_db_type?: string; - - filter_by_environment_tag?: string; - - filter_priority?: GetSqlManageListV3FilterPriorityEnum; - - fuzzy_search_endpoint?: string; - - fuzzy_search_schema_name?: string; - - sort_field?: GetSqlManageListV3SortFieldEnum; - - sort_order?: GetSqlManageListV3SortOrderEnum; +export interface IGetSqlManageRemediationOverviewV1Params { + project_name: string; - page_index: number; + instance_audit_plan_id?: number; - page_size: number; + audit_plan_type?: string; } -export interface IGetSqlManageListV3Return extends IGetSqlManageListResp {} +export interface IGetSqlManageRemediationOverviewV1Return + extends IGetSqlManageRemediationOverviewResp {} diff --git a/packages/shared/lib/api/sqle/service/SqlManage/index.enum.ts b/packages/shared/lib/api/sqle/service/SqlManage/index.enum.ts index 0d04710fc9..331d8041db 100644 --- a/packages/shared/lib/api/sqle/service/SqlManage/index.enum.ts +++ b/packages/shared/lib/api/sqle/service/SqlManage/index.enum.ts @@ -102,6 +102,14 @@ export enum exportSqlManageV1SortOrderEnum { 'desc' = 'desc' } +export enum exportSqlManageRemediationV1ExportScopeEnum { + 'project' = 'project', + + 'data_source' = 'data_source', + + 'scan_task' = 'scan_task' +} + export enum GetSqlManageListV2FilterSourceEnum { 'audit_plan' = 'audit_plan', @@ -136,6 +144,18 @@ export enum GetSqlManageListV2FilterPriorityEnum { 'low' = 'low' } +export enum GetSqlManageListV2FilterRemediationStatusEnum { + 'resolved' = 'resolved', + + 'partially_fixed' = 'partially_fixed', + + 'unchanged' = 'unchanged', + + 'deteriorated' = 'deteriorated', + + 'newly_discovered' = 'newly_discovered' +} + export enum GetSqlManageListV2SortFieldEnum { 'first_appear_timestamp' = 'first_appear_timestamp', diff --git a/packages/shared/lib/api/sqle/service/SqlManage/index.ts b/packages/shared/lib/api/sqle/service/SqlManage/index.ts index 535cf514a6..3371af7cff 100644 --- a/packages/shared/lib/api/sqle/service/SqlManage/index.ts +++ b/packages/shared/lib/api/sqle/service/SqlManage/index.ts @@ -18,6 +18,8 @@ import { IBatchUpdateSqlManageParams, IBatchUpdateSqlManageReturn, IExportSqlManageV1Params, + IExportGlobalSqlManageRemediationV1Params, + IExportSqlManageRemediationV1Params, IGetSqlManageRuleTipsParams, IGetSqlManageRuleTipsReturn, ISendSqlManageParams, @@ -28,9 +30,10 @@ import { IGetSqlManageSqlAnalysisChartV1Return, IGetSqlManageListV2Params, IGetSqlManageListV2Return, - IExportSqlManageV2Params, - IGetSqlManageListV3Params, - IGetSqlManageListV3Return + IGetSqlManageRemediationV1Params, + IGetSqlManageRemediationV1Return, + IGetSqlManageRemediationOverviewV1Params, + IGetSqlManageRemediationOverviewV1Return } from './index.d'; class SqlManageService extends ServiceBase { @@ -118,6 +121,34 @@ class SqlManageService extends ServiceBase { ); } + public exportGlobalSqlManageRemediationV1( + params: IExportGlobalSqlManageRemediationV1Params = {}, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + + return this.get( + `/v1/sql_manages/remediation_exports`, + paramsData, + options + ); + } + + public exportSqlManageRemediationV1( + params: IExportSqlManageRemediationV1Params, + options?: AxiosRequestConfig + ) { + const paramsData = this.cloneDeep(params); + const project_name = paramsData.project_name; + delete paramsData.project_name; + + return this.get( + `/v1/projects/${project_name}/sql_manages/remediation_exports`, + paramsData, + options + ); + } + public GetSqlManageRuleTips( params: IGetSqlManageRuleTipsParams, options?: AxiosRequestConfig @@ -199,31 +230,34 @@ class SqlManageService extends ServiceBase { ); } - public exportSqlManageV2( - params: IExportSqlManageV2Params, + public GetSqlManageRemediationV1( + params: IGetSqlManageRemediationV1Params, options?: AxiosRequestConfig ) { const paramsData = this.cloneDeep(params); const project_name = paramsData.project_name; delete paramsData.project_name; - return this.get( - `/v2/projects/${project_name}/sql_manages/exports`, + const sql_manage_id = paramsData.sql_manage_id; + delete paramsData.sql_manage_id; + + return this.get( + `/v1/projects/${project_name}/sql_manages/${sql_manage_id}/remediation`, paramsData, options ); } - public GetSqlManageListV3( - params: IGetSqlManageListV3Params, + public getSqlManageRemediationOverviewV1( + params: IGetSqlManageRemediationOverviewV1Params, options?: AxiosRequestConfig ) { const paramsData = this.cloneDeep(params); const project_name = paramsData.project_name; delete paramsData.project_name; - return this.get( - `/v3/projects/${project_name}/sql_manages`, + return this.get( + `/v1/projects/${project_name}/sql_manages/remediation_overview`, paramsData, options ); diff --git a/packages/shared/lib/api/sqle/service/common.d.ts b/packages/shared/lib/api/sqle/service/common.d.ts index 57804bfc7f..80fadb2a79 100644 --- a/packages/shared/lib/api/sqle/service/common.d.ts +++ b/packages/shared/lib/api/sqle/service/common.d.ts @@ -3597,6 +3597,16 @@ export interface ISqlManage { remark?: string; + remediation_status?: string; + + first_audit_missing?: boolean; + + first_audit_result?: IAuditResult[]; + + first_audit_time?: string; + + rule_diff?: IRuleDiff; + schema_name?: string; source?: ISource; @@ -5094,12 +5104,76 @@ export interface IWorkflowStepResV2 { workflow_step_id?: number; } -export interface IBatchCompleteWorkflowsReqV3 { - workflow_list?: ICompleteWorkflowReq[]; +export interface IRuleDiff { + new?: IAuditResult[]; + + resolved?: IAuditResult[]; + + unchanged?: IAuditResult[]; } -export interface ICompleteWorkflowReq { - desc?: string; +export interface ISqlManageRemediation { + first_audit_missing?: boolean; - workflow_id?: string; + first_audit_result?: IAuditResult[]; + + first_audit_time?: string; + + id?: number; + + latest_audit_result?: IAuditResult[]; + + latest_audit_time?: string; + + remediation_status?: string; + + rule_diff?: IRuleDiff; + + sql?: string; + + sql_fingerprint?: string; +} + +export interface IGetSqlManageRemediationResp { + code?: number; + + data?: ISqlManageRemediation; + + message?: string; +} + +export interface ISqlManageRemediationOverviewStatusCount { + deteriorated?: number; + + newly_discovered?: number; + + partially_fixed?: number; + + resolved?: number; + + unchanged?: number; +} + +export interface ISqlManageRemediationOverview { + first_audit_missing_num?: number; + + first_score?: number; + + latest_score?: number; + + remediation_rate?: number; + + score_change?: number; + + sql_total_num?: number; + + remediation_status_count?: ISqlManageRemediationOverviewStatusCount; +} + +export interface IGetSqlManageRemediationOverviewResp { + code?: number; + + data?: ISqlManageRemediationOverview; + + message?: string; } diff --git a/packages/sqle/src/data/EmitterKey.ts b/packages/sqle/src/data/EmitterKey.ts index 1650a2d028..58f4ec1475 100644 --- a/packages/sqle/src/data/EmitterKey.ts +++ b/packages/sqle/src/data/EmitterKey.ts @@ -27,6 +27,7 @@ enum EmitterKey { Refresh_Sql_Management_Conf_Overview_List = 'Refresh_Sql_Management_Conf_Overview_List', Refresh_Sql_Management_Conf_Detail_Sql_List = 'Refresh_Sql_Management_Conf_Detail_Sql_List', Export_Sql_Management_Conf_Detail_Sql_List = 'Export_Sql_Management_Conf_Detail_Sql_List', + Export_Sql_Management_Conf_Detail_Remediation = 'Export_Sql_Management_Conf_Detail_Remediation', Refresh_Sql_management_Exception_List = 'Refresh_Sql_management_Exception_List', diff --git a/packages/sqle/src/locale/en-US/managementConf.ts b/packages/sqle/src/locale/en-US/managementConf.ts index ba29a61570..a515518e1a 100644 --- a/packages/sqle/src/locale/en-US/managementConf.ts +++ b/packages/sqle/src/locale/en-US/managementConf.ts @@ -111,6 +111,19 @@ export default { auditImmediately: 'Audit immediately', auditImmediatelySuccessTips: 'Audit successfully', exportTips: 'Exporting scan task details', + remediationExport: 'SQL remediation', + remediationExportTips: 'Exporting SQL management remediation report', + remediationExportSuccessTips: + 'Export SQL management remediation report successfully', + remediationOverview: { + title: 'Remediation overview', + sqlTotal: 'SQL total', + firstScore: 'First score', + latestScore: 'Latest score', + scoreChange: 'Score change', + remediationRate: 'Remediation rate', + loadFailed: 'Failed to load remediation overview: {{message}}' + }, overview: { title: 'Overview', column: { diff --git a/packages/sqle/src/locale/en-US/sqlManagement.ts b/packages/sqle/src/locale/en-US/sqlManagement.ts index a52c99dd8c..cd379f78de 100644 --- a/packages/sqle/src/locale/en-US/sqlManagement.ts +++ b/packages/sqle/src/locale/en-US/sqlManagement.ts @@ -6,11 +6,25 @@ export default { export: 'Export', exporting: 'Exporting file', exportSuccessTips: 'Export file successfully', - exportFormatModal: { - title: 'Select export file format' - } + remediationExport: 'SQL remediation', + remediationExporting: 'Exporting SQL management remediation report', + remediationExportSuccessTips: + 'Export SQL management remediation report successfully' } }, + remediationReport: { + pageTitle: 'SQL management remediation report', + description: + 'Export SQL management remediation tracking data in global scope. The Excel file contains Overview, Rule summary and Details.', + exportButton: 'Export SQL management remediation report', + exporting: 'Exporting SQL management remediation report', + exportSuccessTips: 'Export SQL management remediation report successfully', + scopeTitle: 'Export scope', + scopeContent: + 'Global scope: includes SQL management remediation data across all projects available to platform administrators.', + permissionTips: + 'Only platform administrators / global operators can see and export this report.' + }, statistics: { SQLTotalNum: 'SQL total', problemSQlNum: 'Problem SQL', @@ -67,9 +81,17 @@ export default { occurrenceCount: 'Occurrence count', personInCharge: 'Person in charge', status: 'Status', + remediationStatus: 'Remediation status', comment: 'Comment', endpoints: 'Endpoint info' }, + remediationStatus: { + resolved: 'Resolved', + partially_fixed: 'Partially fixed', + unchanged: 'Unchanged', + deteriorated: 'Deteriorated', + newly_discovered: 'Newly discovered' + }, filter: { time: 'Time range', status: { diff --git a/packages/sqle/src/locale/zh-CN/managementConf.ts b/packages/sqle/src/locale/zh-CN/managementConf.ts index d49f3a62d6..1252395e15 100644 --- a/packages/sqle/src/locale/zh-CN/managementConf.ts +++ b/packages/sqle/src/locale/zh-CN/managementConf.ts @@ -116,6 +116,18 @@ export default { auditImmediately: '立即审核', auditImmediatelySuccessTips: '审核成功', exportTips: '正在导出扫描任务详情', + remediationExport: 'SQL 管控整改', + remediationExportTips: '正在导出 SQL 管控整改报表', + remediationExportSuccessTips: 'SQL 管控整改报表导出成功', + remediationOverview: { + title: '整改概览', + sqlTotal: 'SQL 总数', + firstScore: '首次评分', + latestScore: '最末次评分', + scoreChange: '评分变化', + remediationRate: '整改率', + loadFailed: '整改概览加载失败:{{message}}' + }, overview: { title: '概览', column: { diff --git a/packages/sqle/src/locale/zh-CN/sqlManagement.ts b/packages/sqle/src/locale/zh-CN/sqlManagement.ts index 9d1f95c2e4..be0701d236 100644 --- a/packages/sqle/src/locale/zh-CN/sqlManagement.ts +++ b/packages/sqle/src/locale/zh-CN/sqlManagement.ts @@ -6,11 +6,23 @@ export default { export: '导出', exporting: '正在导出文件', exportSuccessTips: '导出文件成功', - exportFormatModal: { - title: '选择导出文件格式' - } + remediationExport: 'SQL 管控整改', + remediationExporting: '正在导出 SQL 管控整改报表', + remediationExportSuccessTips: 'SQL 管控整改报表导出成功' } }, + remediationReport: { + pageTitle: 'SQL 管控整改报表', + description: + '导出全局范围内 SQL 管控整改追踪数据,Excel 包含概览、规则维度汇总和明细。', + exportButton: '导出 SQL 管控整改报表', + exporting: '正在导出 SQL 管控整改报表', + exportSuccessTips: 'SQL 管控整改报表导出成功', + scopeTitle: '导出范围', + scopeContent: + '全局范围:覆盖当前用户有平台管理权限的全部项目 SQL 管控整改数据', + permissionTips: '仅平台超管 / 全局运维可见并可导出。' + }, statistics: { SQLTotalNum: 'SQL总数', problemSQlNum: '问题SQL数', @@ -92,9 +104,17 @@ export default { occurrenceCount: '出现数量', personInCharge: '负责人', status: '状态', + remediationStatus: '整改状态', comment: '备注', endpoints: '端点信息' }, + remediationStatus: { + resolved: '已整改', + partially_fixed: '部分整改', + unchanged: '未变化', + deteriorated: '恶化', + newly_discovered: '新发现' + }, filter: { time: '时间范围', status: { @@ -127,5 +147,18 @@ export default { statusReport: { title: 'SQL审核结果' } + }, + remediationCompare: { + tab: '整改对比', + title: '整改对比', + description: '对比首次审核与最末次审核结果,展示规则整改变化', + firstAuditMissing: '无首次审核快照,当前按最末次结果展示新发现问题。', + firstAuditResult: '首次审核结果', + latestAuditResult: '最末次审核结果', + ruleDiffTitle: '规则差异', + resolved: '已解决规则', + new: '新增规则', + unchanged: '维持规则', + emptyRules: '无命中规则' } }; diff --git a/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/SqlAnalyze.tsx b/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/SqlAnalyze.tsx index e33b5b516a..f228ae5665 100644 --- a/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/SqlAnalyze.tsx +++ b/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/SqlAnalyze.tsx @@ -12,6 +12,8 @@ import { SqlAnalyzeContStyleWrapper, SqlContStyleWrapper } from './style'; import useTableSchema from './useTableSchema'; import useSQLExecPlan from './useSQLExecPlan'; import { SqlAnalyzeProps } from '.'; +import RemediationCompare from '../SqlManage/RemediationCompare'; + const SqlAnalyze: React.FC = (props) => { const { t } = useTranslation(); const { @@ -20,22 +22,8 @@ const SqlAnalyze: React.FC = (props) => { errorMessage, loading = false, performanceStatistics, - errorType = 'error', - sqlExecPlanCostDataSource, - getSqlExecPlanCostDataSource, - getSqlExecPlanCostDataSourceLoading, - getSqlExecPlanCostDataSourceError, - showExecPlanCostChart, - initTime, - selectedPoint, - setSelectedPoint, - onCreateSqlOptimization, - onViewOptimizationResult, - optimizationRecordId, - createSqlOptimizationLoading, - allowSqlOptimization, - getPerformanceStatistics, - isPerformanceInfoLoaded + remediationCompare, + errorType = 'error' } = props; const { generateTableSchemaContent } = useTableSchema(); const { generateSQLExecPlanContent } = useSQLExecPlan({ @@ -76,16 +64,18 @@ const SqlAnalyze: React.FC = (props) => { ) ) { return [ + { label: t('sqlAnalyze.sqlExplain'), value: 'sql' }, { - label: t('sqlAnalyze.sqlExplain'), - value: 'sql' + label: t('sqlManagement.remediationCompare.tab'), + value: 'remediation' } ]; } return [ + { label: t('sqlAnalyze.sqlExplain'), value: 'sql' }, { - label: t('sqlAnalyze.sqlExplain'), - value: 'sql' + label: t('sqlManagement.remediationCompare.tab'), + value: 'remediation' } ].concat( (tableMetas?.table_meta_items ?? []).map((table) => { @@ -120,6 +110,9 @@ const SqlAnalyze: React.FC = (props) => { ...sqlExplain, ...performanceStatistics })} + {tabStatus === 'remediation' && ( + + )} {tableMetas?.table_meta_items?.map((table) => { return ( diff --git a/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/index.tsx b/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/index.tsx index 417d3dcd60..b0aa92c9d5 100644 --- a/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/index.tsx +++ b/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/index.tsx @@ -2,6 +2,7 @@ import { ResultStatusType } from 'antd/es/result'; import SqlAnalyze from './SqlAnalyze'; import { IPerformanceStatistics, + ISqlManageRemediation, ISQLExplain, ITableMeta, ITableMetas, @@ -17,6 +18,7 @@ export type SqlAnalyzeProps = { tableMetas?: ITableMetas; sqlExplain?: ISQLExplain; performanceStatistics?: IPerformanceStatistics; + remediationCompare?: ISqlManageRemediation; loading?: boolean; sqlExecPlanCostDataSource?: IChartPoint[]; getSqlExecPlanCostDataSourceLoading?: boolean; diff --git a/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/style.ts b/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/style.ts index 7ed0bd8f81..81e077d4ea 100644 --- a/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/style.ts +++ b/packages/sqle/src/page/SqlAnalyze/SqlAnalyze/style.ts @@ -102,37 +102,39 @@ export const SqlContStyleWrapper = styled('section')` } } - .diff-highlight { - color: ${({ theme }) => theme.sharedTheme.components.basicTag.Grape.color}; - } + .remediation-compare-wrapper { + padding: 24px 40px; - .history-exec-plan-wrapper { - scroll-margin-top: 60px; - } -`; + .remediation-section-space { + width: 100%; + } -export const SqlAnalyzeCostLineChartStyleWrapper = styled('section')` - .filter-wrapper { - margin-bottom: 16px; - display: flex; - padding: 24px 40px 12px; - justify-content: space-between; - align-items: center; - width: 100%; - border-bottom: 1px solid - ${({ theme }) => theme.sharedTheme.uiToken.colorBorderSecondary}; + .remediation-header { + display: flex; + align-items: center; + justify-content: space-between; + } - h3 { - font-size: 16px; - font-style: normal; - font-weight: 600; - line-height: 24px; - color: ${({ theme }) => theme.sharedTheme.uiToken.colorText}; - margin: 0; + .remediation-title { + margin-bottom: 0; } - } - .chart-wrapper { - padding: 10px 40px; + .remediation-columns, + .remediation-diff-columns { + display: grid; + grid-column-gap: 16px; + } + + .remediation-columns { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .remediation-diff-columns { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .ant-list-item { + align-items: flex-start; + } } `; diff --git a/packages/sqle/src/page/SqlAnalyze/SqlManage/RemediationCompare.tsx b/packages/sqle/src/page/SqlAnalyze/SqlManage/RemediationCompare.tsx new file mode 100644 index 0000000000..3241044a87 --- /dev/null +++ b/packages/sqle/src/page/SqlAnalyze/SqlManage/RemediationCompare.tsx @@ -0,0 +1,91 @@ +import { Empty, Alert, Card, Space, Typography } from 'antd'; +import { useTranslation } from 'react-i18next'; +import AuditResultMessage from '../../../components/AuditResultMessage'; +import { ISqlManageRemediation } from '@actiontech/shared/lib/api/sqle/service/common'; +import RemediationStatusTag from '../../SqlManagement/component/SQLEEIndex/RemediationStatusTag'; + +type RemediationCompareProps = { + data?: ISqlManageRemediation; +}; + +const ruleNames = (rules?: ISqlManageRemediation['rule_diff']) => ({ + resolved: rules?.resolved?.map((rule) => rule.rule_name).filter(Boolean), + newRules: rules?.new?.map((rule) => rule.rule_name).filter(Boolean), + unchanged: rules?.unchanged?.map((rule) => rule.rule_name).filter(Boolean) +}); + +const RuleList: React.FC<{ title: string; rules?: string[] }> = ({ + title, + rules +}) => { + const { t } = useTranslation(); + + return ( + + {title} + {rules?.length ? ( + rules.map((rule) => ( + {rule} + )) + ) : ( + + {t('sqlManagement.remediationCompare.emptyRules')} + + )} + + ); +}; + +const RemediationCompare: React.FC = ({ data }) => { + const { t } = useTranslation(); + + if (!data) { + return ; + } + + const rules = ruleNames(data.rule_diff); + + return ( + + + + + {t('sqlManagement.remediationCompare.description')} + + {data.first_audit_missing && ( + + )} + + + + + + + + + + + + + + + + + + ); +}; + +export default RemediationCompare; diff --git a/packages/sqle/src/page/SqlAnalyze/SqlManage/index.tsx b/packages/sqle/src/page/SqlAnalyze/SqlManage/index.tsx index bdf209f9a5..f0f830a596 100644 --- a/packages/sqle/src/page/SqlAnalyze/SqlManage/index.tsx +++ b/packages/sqle/src/page/SqlAnalyze/SqlManage/index.tsx @@ -5,6 +5,7 @@ import { ResponseCode } from '../../../data/common'; import SqlManage from '@actiontech/shared/lib/api/sqle/service/SqlManage'; import { IPerformanceStatistics, + ISqlManageRemediation, ISQLExplain, ITableMetas } from '@actiontech/shared/lib/api/sqle/service/common'; @@ -30,65 +31,46 @@ const SQLManageAnalyze = () => { const [tableMetas, setTableMetas] = useState(); const [performanceStatistics, setPerformancesStatistics] = useState(); - + const [remediationCompare, setRemediationCompare] = + useState(); const [ loading, { setTrue: startGetSqlAnalyze, setFalse: getSqlAnalyzeFinish } ] = useBoolean(); const [errorType, setErrorType] = useState('error'); - const [isPerformanceInfoLoaded, { setTrue: setPerformanceInfoLoaded }] = - useBoolean(); - - const { - setOptimizationCreationParams, - onCreateSqlOptimization, - onViewOptimizationResult, - optimizationRecordId, - createSqlOptimizationLoading, - allowSqlOptimization - } = useSqlOptimization(); - - const getSqlAnalyze = useCallback( - async (affectRowsEnabled = false) => { - startGetSqlAnalyze(); - try { - const res = await SqlManage.GetSqlManageSqlAnalysisV1({ + const getSqlAnalyze = useCallback(async () => { + startGetSqlAnalyze(); + try { + const [res, remediationRes] = await Promise.all([ + SqlManage.GetSqlManageSqlAnalysisV1({ sql_manage_id: urlParams.sqlManageId ?? '', - project_name: projectName, - affectRowsEnabled - }); - if (res.data.code === ResponseCode.SUCCESS) { - if (affectRowsEnabled) { - setPerformanceInfoLoaded(); - } - setErrorMessage(''); - const { data } = res.data; - const queryParams = extractQueries( - ROUTE_PATHS.SQLE.SQL_MANAGEMENT.analyze - ); - setSqlExplain(data?.sql_explain); - setTableMetas(data?.table_metas); - setPerformancesStatistics(data?.performance_statistics); - setOptimizationCreationParams({ - instance_name: queryParams?.instance_name, - schema_name: queryParams?.schema, - sql_content: data?.sql_explain?.sql - }); - } else { + project_name: projectName + }), + SqlManage.GetSqlManageRemediationV1({ + sql_manage_id: urlParams.sqlManageId ?? '', + project_name: projectName + }) + ]); + + if (res.data.code !== ResponseCode.SUCCESS) { + if (remediationRes.data.code !== ResponseCode.SUCCESS) { if (res.data.code === ResponseCode.NotSupportDML) { setErrorType('info'); } else { - if (res.data.code === ResponseCode.NotSupportDML) { - setErrorType('info'); - } else { - setErrorType('error'); - } - setErrorMessage(res.data.message ?? ''); + setErrorType('error'); } + setErrorMessage(res.data.message ?? ''); + return; } - } finally { - getSqlAnalyzeFinish(); + } + + setErrorMessage(''); + setSqlExplain(res.data.data?.sql_explain); + setTableMetas(res.data.data?.table_metas); + setPerformancesStatistics(res.data.data?.performance_statistics); + if (remediationRes.data.code === ResponseCode.SUCCESS) { + setRemediationCompare(remediationRes.data.data); } }, [ @@ -125,34 +107,15 @@ const SQLManageAnalyze = () => { }, [getSqlAnalyze, getSqlExecPlanCostDataSource]); return ( - <> - - - + ); }; diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/RemediationStatusTag.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/RemediationStatusTag.tsx new file mode 100644 index 0000000000..daa9ac7b35 --- /dev/null +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/RemediationStatusTag.tsx @@ -0,0 +1,36 @@ +import { BasicTag } from '@actiontech/shared'; +import { t } from '../../../../locale'; + +export const remediationStatusOptions = [ + 'resolved', + 'partially_fixed', + 'unchanged', + 'deteriorated', + 'newly_discovered' +]; + +type RemediationStatusTagProps = { + status?: string; +}; + +const remediationStatusColor: Record = { + resolved: 'green', + partially_fixed: 'blue', + unchanged: 'gray', + deteriorated: 'red', + newly_discovered: 'orange' +}; + +const RemediationStatusTag: React.FC = ({ + status +}) => { + const currentStatus = status || 'unchanged'; + + return ( + + {t(`sqlManagement.table.remediationStatus.${currentStatus}`)} + + ); +}; + +export default RemediationStatusTag; diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/column.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/column.tsx index 0764712fa1..72fd74cb01 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/column.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/column.tsx @@ -13,14 +13,11 @@ import { BasicToolTip, CustomAvatar, EditText } from '@actiontech/dms-kit'; import { SQLRenderer, TypedLink } from '@actiontech/shared'; import { Avatar, Space } from 'antd'; import StatusTag from './StatusTag'; -import { BasicTag, basicTooltipCommonProps } from '@actiontech/dms-kit'; -import { BasicTypographyEllipsis } from '@actiontech/shared'; -import { SqlManageAuditStatusEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; -import { - PERMISSIONS, - PermissionsConstantType -} from '@actiontech/shared/lib/features'; -import { ROUTE_PATHS } from '@actiontech/dms-kit'; +import { BasicTag, BasicTypographyEllipsis } from '@actiontech/shared'; +import { ACTIONTECH_TABLE_ACTION_BUTTON_WIDTH } from '@actiontech/shared/lib/components/ActiontechTable/hooks/useTableAction'; +import { SQLAuditRecordListUrlParamsKey } from './index.data'; +import RemediationStatusTag from './RemediationStatusTag'; + export type SqlManagementTableFilterParamType = PageInfoWithoutIndexAndSize< IGetSqlManageListV3Params, 'fuzzy_search_sql_fingerprint' | 'filter_status' | 'project_name' @@ -31,6 +28,7 @@ export type ExtraFilterMetaType = ISqlManage & { filter_instance_id?: string; filter_audit_level?: string; filter_rule_name?: string; + filter_remediation_status?: string; time?: string; }; export const ExtraFilterMeta: () => ActiontechTableFilterMeta< @@ -97,6 +95,15 @@ export const ExtraFilterMeta: () => ActiontechTableFilterMeta< filterLabel: t('sqlManagement.table.filter.rule'), checked: false } + ], + [ + 'filter_remediation_status', + { + filterCustomType: 'select', + filterKey: 'filter_remediation_status', + filterLabel: t('sqlManagement.table.column.remediationStatus'), + checked: false + } ] ]); }; @@ -264,6 +271,11 @@ const sqlManagementColumn: ( return '-'; } }, + { + dataIndex: 'remediation_status', + title: () => t('sqlManagement.table.column.remediationStatus'), + render: (status) => + }, // { // dataIndex: 'first_appear_timestamp', // title: () => t('sqlManagement.table.column.firstOccurrence'), diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.ts b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.ts index 3451cdb06e..3774e3f40c 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.ts +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/hooks/useGetTableFilterInfo.ts @@ -6,14 +6,12 @@ import useInstance from '../../../../../hooks/useInstance'; import useRuleTips from './useRuleTips'; import { ExtraFilterMetaType } from '../column'; import useSourceTips from './useSourceTips'; -import { useTypedQuery } from '@actiontech/shared'; -import { ROUTE_PATHS } from '@actiontech/dms-kit'; -import useServiceEnvironment from '../../../../../hooks/useServiceEnvironment'; +import { remediationStatusOptions } from '../RemediationStatusTag'; +import { useTranslation } from 'react-i18next'; const useGetTableFilterInfo = () => { - const { projectName, projectID } = useCurrentProject(); - - const extractQueries = useTypedQuery(); + const { t } = useTranslation(); + const { projectName } = useCurrentProject(); const { generateAuditLevelSelectOptions } = useStaticStatus(); @@ -82,6 +80,15 @@ const useGetTableFilterInfo = () => { loading: getRuleTipsLoading, popupMatchSelectWidth: 400 } + ], + [ + 'filter_remediation_status', + { + options: remediationStatusOptions.map((status) => ({ + label: t(`sqlManagement.table.remediationStatus.${status}`), + value: status + })) + } ] ]); }, [ @@ -94,7 +101,8 @@ const useGetTableFilterInfo = () => { getSourceTipsLoading, generateAuditLevelSelectOptions, generateRuleTipsSelectOptions, - getRuleTipsLoading + getRuleTipsLoading, + t ]); return { diff --git a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx index 7e98d889d0..c42e439252 100644 --- a/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx +++ b/packages/sqle/src/page/SqlManagement/component/SQLEEIndex/index.tsx @@ -1,11 +1,7 @@ import { useTranslation } from 'react-i18next'; import { useBoolean, useRequest } from 'ahooks'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { BasicButton, PageHeader } from '@actiontech/dms-kit'; -import { useTypedQuery } from '@actiontech/shared'; -import { useExportFormatModal } from '../../../../hooks/useExportFormatModal'; -import ExportFormatModal from '../../../../components/ExportFormatModal'; -import { GetAuditPlanSQLExportReqV1ExportFormatEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; +import { BasicButton, EmptyBox, PageHeader } from '@actiontech/shared'; import SQLStatistics, { ISQLStatisticsProps } from '../SQLStatistics'; import { useTableFilterContainer, @@ -28,13 +24,13 @@ import { import { ResponseCode } from '@actiontech/dms-kit'; import StatusFilter, { TypeStatus } from './StatusFilter'; import { - GetSqlManageListV3FilterPriorityEnum, - GetSqlManageListV3FilterStatusEnum, - GetSqlManageListV3SortFieldEnum, - GetSqlManageListV3SortOrderEnum, - exportSqlManageV2FilterPriorityEnum, - exportSqlManageV2FilterStatusEnum, - exportSqlManageV2ExportFormatEnum + GetSqlManageListV2FilterPriorityEnum, + GetSqlManageListV2FilterStatusEnum, + GetSqlManageListV2SortFieldEnum, + GetSqlManageListV2SortOrderEnum, + exportSqlManageV1FilterPriorityEnum, + exportSqlManageV1FilterStatusEnum, + exportSqlManageRemediationV1ExportScopeEnum } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.enum'; import sqlManagementColumn, { ExtraFilterMeta, @@ -48,8 +44,8 @@ import { import { ModalName } from '../../../../data/ModalName'; import { SorterResult, TableRowSelection } from 'antd/es/table/interface'; import { ISqlManage } from '@actiontech/shared/lib/api/sqle/service/common'; -import { Spin, message } from 'antd'; -import SqlManagementModals from './Modals'; +import { Space, Spin, message } from 'antd'; +import SqlManagementModal from './Modal'; import EmitterKey from '../../../../data/EmitterKey'; import EventEmitter from '../../../../utils/EventEmitter'; import { DB_TYPE_RULE_NAME_SEPARATOR } from './hooks/useRuleTips'; @@ -369,13 +365,10 @@ const SQLEEIndex = () => { exportButtonDisabled, { setFalse: finishExport, setTrue: startExport } ] = useBoolean(false); - const { - exportFormatModalVisible, - showExportFormatModal, - hideExportFormatModal, - selectedExportFormat, - setSelectedExportFormat - } = useExportFormatModal(GetAuditPlanSQLExportReqV1ExportFormatEnum.csv); + const [ + remediationExportButtonDisabled, + { setFalse: finishRemediationExport, setTrue: startRemediationExport } + ] = useBoolean(false); const handleExport = () => { hideExportFormatModal(); startExport(); @@ -417,6 +410,36 @@ const SQLEEIndex = () => { finishExport(); }); }; + + const handleRemediationExport = () => { + if (!actionPermission || projectArchive) { + return; + } + startRemediationExport(); + const hideLoading = messageApi.loading( + t('sqlManagement.pageHeader.action.remediationExporting') + ); + + SqlManage.exportSqlManageRemediationV1( + { + project_name: projectName, + export_scope: exportSqlManageRemediationV1ExportScopeEnum.project + }, + { responseType: 'blob' } + ) + .then((res) => { + if (res.status === 200) { + messageApi.success( + t('sqlManagement.pageHeader.action.remediationExportSuccessTips') + ); + } + }) + .finally(() => { + hideLoading(); + finishRemediationExport(); + }); + }; + useEffect(() => { EventEmitter.subscribe(EmitterKey.Refresh_SQL_Management, refresh); return () => { @@ -529,13 +552,24 @@ const SQLEEIndex = () => { } - disabled={exportButtonDisabled} - > - {t('sqlManagement.pageHeader.action.export')} - + + + } + disabled={remediationExportButtonDisabled} + > + {t('sqlManagement.pageHeader.action.remediationExport')} + + + } + disabled={exportButtonDisabled} + > + {t('sqlManagement.pageHeader.action.export')} + + } /> void; exportDone: () => void; - instanceName: string; + remediationExportPending: () => void; + remediationExportDone: () => void; }; export type ScanTypeSqlTableDataSourceItem = { [key in string]: string }; diff --git a/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/indx.tsx b/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/indx.tsx index 9b72715336..4ff20b980b 100644 --- a/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/indx.tsx +++ b/packages/sqle/src/page/SqlManagementConf/Detail/ScanTypeSqlCollection/indx.tsx @@ -14,6 +14,7 @@ import { ScanTypeSqlCollectionTableStyleWrapper } from './style'; import instance_audit_plan from '@actiontech/shared/lib/api/sqle/service/instance_audit_plan'; +import SqlManage from '@actiontech/shared/lib/api/sqle/service/SqlManage'; import { useCurrentProject, useCurrentUser @@ -38,12 +39,29 @@ import { IGetInstanceAuditPlanSQLDataV1Params, IGetInstanceAuditPlanSQLExportV1Params } from '@actiontech/shared/lib/api/sqle/service/instance_audit_plan/index.d'; -import { GetAuditPlanSQLExportReqV1ExportFormatEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; -import { mergeFilterButtonMeta } from '@actiontech/dms-kit/es/components/ActiontechTable/hooks/useTableFilterContainer'; -import { ResponseCode } from '@actiontech/dms-kit'; -import { message } from 'antd'; -import { ROUTE_PATHS } from '@actiontech/dms-kit'; -import { ResultIconRenderProps } from '../../../../components/AuditResultMessage/index.type'; +import { mergeFilterButtonMeta } from '@actiontech/shared/lib/components/ActiontechTable/hooks/useTableFilterContainer'; +import { ResponseCode } from '@actiontech/shared/lib/enum'; +import { Card, Spin, message } from 'antd'; +import { Link } from 'react-router-dom'; +import { exportSqlManageRemediationV1ExportScopeEnum } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.enum'; +import { formatParamsBySeparator } from '@actiontech/shared/lib/utils/Tool'; +import RemediationStatusTag, { + remediationStatusOptions +} from '../../../SqlManagement/component/SQLEEIndex/RemediationStatusTag'; + +const formatOverviewNumber = (value?: number) => { + if (typeof value !== 'number') { + return '-'; + } + return formatParamsBySeparator(value); +}; + +const formatRemediationRate = (value?: number) => { + if (typeof value !== 'number') { + return '-'; + } + return `${(value * 100).toFixed(2)}%`; +}; const BEING_AUDITED = 'being_audited'; const ScanTypeSqlCollection: React.FC = ({ @@ -54,7 +72,8 @@ const ScanTypeSqlCollection: React.FC = ({ instanceType, exportDone, exportPending, - instanceName + remediationExportPending, + remediationExportDone }) => { const { t } = useTranslation(); const { sortableTableColumnFactory, tableFilterMetaFactory } = @@ -196,6 +215,28 @@ const ScanTypeSqlCollection: React.FC = ({ } } ); + + const { + data: remediationOverview, + loading: remediationOverviewLoading, + error: remediationOverviewError + } = useRequest( + () => + SqlManage.getSqlManageRemediationOverviewV1({ + project_name: projectName, + instance_audit_plan_id: Number(instanceAuditPlanId), + audit_plan_type: auditPlanType + }).then((res) => { + if (res.data.code === ResponseCode.SUCCESS) { + return res.data.data; + } + }), + { + ready: + activeTabKey === auditPlanId && !!instanceAuditPlanId && !!auditPlanType + } + ); + const recordAuditResult = useMemo(() => { try { return JSON.parse( @@ -264,6 +305,54 @@ const ScanTypeSqlCollection: React.FC = ({ projectName, t ]); + + useEffect(() => { + const exportScanTypeRemediation = () => { + remediationExportPending(); + const hideLoading = messageApi.loading( + t('managementConf.detail.remediationExportTips'), + 0 + ); + + SqlManage.exportSqlManageRemediationV1( + { + project_name: projectName, + export_scope: exportSqlManageRemediationV1ExportScopeEnum.scan_task, + instance_audit_plan_id: Number(instanceAuditPlanId), + audit_plan_type: auditPlanType + }, + { responseType: 'blob' } + ) + .then((res) => { + if (res.status === 200) { + messageApi.success( + t('managementConf.detail.remediationExportSuccessTips') + ); + } + }) + .finally(() => { + remediationExportDone(); + hideLoading(); + }); + }; + const { unsubscribe } = eventEmitter.subscribe( + EmitterKey.Export_Sql_Management_Conf_Detail_Remediation, + exportScanTypeRemediation + ); + + return () => { + unsubscribe(); + }; + }, [ + auditPlanType, + instanceAuditPlanId, + messageApi, + projectName, + remediationExportDone, + remediationExportPending, + t + ]); + const tableSetting = useMemo(() => { return { tableName: `sql_management_conf_${auditPlanType}`, @@ -295,6 +384,81 @@ const ScanTypeSqlCollection: React.FC = ({ }; return ( + + + {remediationOverviewError ? ( +
+ {t('managementConf.detail.remediationOverview.loadFailed', { + message: getErrorMessage(remediationOverviewError) + })} +
+ ) : ( + <> +
+
+ + {formatOverviewNumber(remediationOverview?.sql_total_num)} + + + {t('managementConf.detail.remediationOverview.sqlTotal')} + +
+
+ + {formatOverviewNumber(remediationOverview?.first_score)} + + + {t('managementConf.detail.remediationOverview.firstScore')} + +
+
+ + {formatOverviewNumber(remediationOverview?.latest_score)} + + + {t('managementConf.detail.remediationOverview.latestScore')} + +
+
+ + {formatOverviewNumber(remediationOverview?.score_change)} + + + {t('managementConf.detail.remediationOverview.scoreChange')} + +
+
+ + {formatRemediationRate( + remediationOverview?.remediation_rate + )} + + + {t( + 'managementConf.detail.remediationOverview.remediationRate' + )} + +
+
+
+ {remediationStatusOptions.map((status) => ( +
+ + + {formatOverviewNumber( + remediationOverview?.remediation_status_count?.[status] + )} + +
+ ))} +
+ + )} +
+
{tableMetas?.filter_meta_list?.length && ( theme.sharedTheme.uiToken.colorTextBase}; + font-size: 24px; + font-weight: 600; + line-height: 32px; + } + + span { + color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextTertiary}; + font-size: 13px; + } + } + + .remediation-overview-status-list { + display: flex; + flex-wrap: wrap; + margin: 8px -8px -8px; + padding-top: 12px; + border-top: 1px solid + ${({ theme }) => theme.sharedTheme.uiToken.colorBorderSecondary}; + } + + .remediation-overview-status { + display: flex; + align-items: center; + margin: 8px; + + strong { + margin-left: 8px; + color: ${({ theme }) => theme.sharedTheme.uiToken.colorTextBase}; + font-size: 16px; + font-weight: 600; + } + } + + .remediation-overview-error { + color: ${({ theme }) => theme.sharedTheme.uiToken.colorError}; + } + .table-describe-column { max-width: 600px; } diff --git a/packages/sqle/src/page/SqlManagementConf/Detail/index.tsx b/packages/sqle/src/page/SqlManagementConf/Detail/index.tsx index 83899a6b97..269f009acb 100644 --- a/packages/sqle/src/page/SqlManagementConf/Detail/index.tsx +++ b/packages/sqle/src/page/SqlManagementConf/Detail/index.tsx @@ -16,22 +16,30 @@ import { TableRefreshButton } from '@actiontech/dms-kit/es/components/Actiontech import { useCallback, useState } from 'react'; import ScanTypeSqlCollection from './ScanTypeSqlCollection/indx'; import { useBoolean, useRequest } from 'ahooks'; -import { useLocation } from 'react-router-dom'; -import { SqleApi } from '@actiontech/shared/lib/api'; -import { useCurrentProject } from '@actiontech/shared/lib/features'; +import { + useLocation, + useNavigate, + useParams, + useSearchParams +} from 'react-router-dom'; +import instance_audit_plan from '@actiontech/shared/lib/api/sqle/service/instance_audit_plan'; +import SqlManage from '@actiontech/shared/lib/api/sqle/service/SqlManage'; +import { + useCurrentProject, + useCurrentUser, + useUserOperationPermission +} from '@actiontech/shared/lib/global'; import { Result, Space } from 'antd'; import { getErrorMessage } from '@actiontech/dms-kit'; import { SQL_MANAGEMENT_CONF_OVERVIEW_TAB_KEY } from './index.data'; import eventEmitter from '../../../utils/EventEmitter'; import EmitterKey from '../../../data/EmitterKey'; import { message } from 'antd'; -import { ResponseCode } from '@actiontech/dms-kit'; -import { SqlManagementConfDetailPageHeaderActions } from './action'; -import { ROUTE_PATHS } from '@actiontech/dms-kit'; -import useScanTypeVerify from '../Common/ConfForm/useScanTypeVerify'; -import { GetAuditPlanSQLExportReqV1ExportFormatEnum } from '@actiontech/shared/lib/api/sqle/service/common.enum'; -import { useExportFormatModal } from '../../../hooks/useExportFormatModal'; -import ExportFormatModal from '../../../components/ExportFormatModal'; +import { ResponseCode } from '@actiontech/shared/lib/enum'; +import { OpPermissionItemOpPermissionTypeEnum } from '@actiontech/shared/lib/api/base/service/common.enum'; +import { DownArrowLineOutlined } from '@actiontech/icons'; +import { exportSqlManageRemediationV1ExportScopeEnum } from '@actiontech/shared/lib/api/sqle/service/SqlManage/index.enum'; + const ConfDetail: React.FC = () => { const { t } = useTranslation(); const { projectID } = useCurrentProject(); @@ -42,13 +50,20 @@ const ConfDetail: React.FC = () => { ROUTE_PATHS.SQLE.SQL_MANAGEMENT_CONF.detail ); const location = useLocation(); - const navigate = useTypedNavigate(); - const { projectName } = useCurrentProject(); + const navigate = useNavigate(); + const { projectName, projectArchive } = useCurrentProject(); + const { isAdmin, isProjectManager } = useCurrentUser(); + const [activeKey, setActiveKey] = useState( SQL_MANAGEMENT_CONF_OVERVIEW_TAB_KEY ); const [exporting, { setTrue: exportPending, setFalse: exportDone }] = useBoolean(); + const [ + remediationExporting, + { setTrue: remediationExportPending, setFalse: remediationExportDone } + ] = useBoolean(); + const [auditing, { setTrue: auditPending, setFalse: auditDone }] = useBoolean(); const [messageApi, contextMessageHolder] = message.useMessage(); @@ -108,6 +123,22 @@ const ConfDetail: React.FC = () => { searchParams?.active_audit_plan_id ] ); + + const hasOpPermission = useMemo(() => { + return isHaveServicePermission( + OpPermissionItemOpPermissionTypeEnum.save_audit_plan, + data?.instance_id + ); + }, [data?.instance_id, isHaveServicePermission]); + + const actionPermission = useMemo(() => { + return isAdmin || isProjectManager(projectName); + }, [isAdmin, isProjectManager, projectName]); + + const remediationExportPermission = useMemo(() => { + return (actionPermission || hasOpPermission) && !projectArchive; + }, [actionPermission, hasOpPermission, projectArchive]); + const items: SegmentedTabsProps['items'] = [ { label: t('managementConf.detail.overview.title'), @@ -134,6 +165,8 @@ const ConfDetail: React.FC = () => { instanceName={data.instance_name ?? ''} exportPending={exportPending} exportDone={exportDone} + remediationExportPending={remediationExportPending} + remediationExportDone={remediationExportDone} /> ) })) ?? []) @@ -152,6 +185,42 @@ const ConfDetail: React.FC = () => { selectedExportFormat ); }; + + const exportScanTypeRemediation = () => { + eventEmitter.emit(EmitterKey.Export_Sql_Management_Conf_Detail_Remediation); + }; + + const exportDataSourceRemediation = () => { + if (!remediationExportPermission || !data?.instance_id) { + return; + } + remediationExportPending(); + const hideLoading = messageApi.loading( + t('managementConf.detail.remediationExportTips'), + 0 + ); + + SqlManage.exportSqlManageRemediationV1( + { + project_name: projectName, + export_scope: exportSqlManageRemediationV1ExportScopeEnum.data_source, + filter_instance_id: data.instance_id + }, + { responseType: 'blob' } + ) + .then((res) => { + if (res.status === 200) { + messageApi.success( + t('managementConf.detail.remediationExportSuccessTips') + ); + } + }) + .finally(() => { + hideLoading(); + remediationExportDone(); + }); + }; + const onAuditImmediately = () => { auditPending(); SqleApi.InstanceAuditPlanService.auditPlanTriggerSqlAuditV1({ @@ -205,10 +274,46 @@ const ConfDetail: React.FC = () => { - {pageHeaderActions.export} - {pageHeaderActions.immediately_audit} + + {t('managementConf.detail.export')} + + + } + > + {t('managementConf.detail.remediationExport')} + + + {hasOpPermission && ( + + {t('managementConf.detail.auditImmediately')} + + )} + + } + > + {t('managementConf.detail.remediationExport')} + + } diff --git a/packages/sqle/src/page/SqlManagementRemediationReport/index.tsx b/packages/sqle/src/page/SqlManagementRemediationReport/index.tsx new file mode 100644 index 0000000000..37e5d904e4 --- /dev/null +++ b/packages/sqle/src/page/SqlManagementRemediationReport/index.tsx @@ -0,0 +1,70 @@ +import { BasicButton, PageHeader } from '@actiontech/shared'; +import SqlManage from '@actiontech/shared/lib/api/sqle/service/SqlManage'; +import { DownArrowLineOutlined } from '@actiontech/icons'; +import { Card, Space, Typography, message } from 'antd'; +import { useBoolean } from 'ahooks'; +import { useTranslation } from 'react-i18next'; + +const SqlManagementRemediationReport = () => { + const { t } = useTranslation(); + const [messageApi, messageContextHolder] = message.useMessage(); + const [exporting, { setTrue: startExport, setFalse: finishExport }] = + useBoolean(false); + + const handleExport = () => { + startExport(); + const hideLoading = messageApi.loading( + t('sqlManagement.remediationReport.exporting') + ); + + SqlManage.exportGlobalSqlManageRemediationV1({}, { responseType: 'blob' }) + .then((res) => { + if (res.status === 200) { + messageApi.success( + t('sqlManagement.remediationReport.exportSuccessTips') + ); + } + }) + .finally(() => { + hideLoading(); + finishExport(); + }); + }; + + return ( +
+ {messageContextHolder} + } + onClick={handleExport} + disabled={exporting} + > + {t('sqlManagement.remediationReport.exportButton')} + + } + /> + + + + {t('sqlManagement.remediationReport.scopeTitle')} + + + {t('sqlManagement.remediationReport.description')} + + + {t('sqlManagement.remediationReport.scopeContent')} + + + {t('sqlManagement.remediationReport.permissionTips')} + + + +
+ ); +}; + +export default SqlManagementRemediationReport; diff --git a/packages/sqle/src/router/config.tsx b/packages/sqle/src/router/config.tsx index 37b30cc5b8..c81abef728 100644 --- a/packages/sqle/src/router/config.tsx +++ b/packages/sqle/src/router/config.tsx @@ -196,6 +196,10 @@ const UpdateCustomRule = React.lazy( ); const ReportStatistics = React.lazy(() => import('../page/ReportStatistics')); +const SqlManagementRemediationReport = React.lazy( + () => import('../page/SqlManagementRemediationReport') +); + const PushRuleConfiguration = React.lazy( () => import('../page/PushRuleConfiguration') ); @@ -499,7 +503,14 @@ export const projectDetailRouterConfig: RouterConfigItem[] = [ export const globalRouterConfig: RouterConfigItem[] = [ { - path: ROUTE_PATHS.SQLE.REPORT_STATISTICS.index.path, + path: 'sqle/sql-management-remediation-report', + element: , + key: 'sqlManagementRemediationReport', + role: [SystemRole.admin] + }, + { + path: 'sqle/report-statistics', + label: 'menu.reportStatistics', element: , key: 'reportStatistics', permission: PERMISSIONS.PAGES.SQLE.REPORT_STATISTICS