├── .env.test ├── .eslintrc ├── .github └── workflows │ ├── main.yml │ └── pr-release-2-main.yml ├── .gitignore ├── .gitlab-ci.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── QR_code.png ├── README.md ├── craco.config.js ├── package.json ├── public ├── favicon.png ├── index.html └── static │ ├── css │ ├── antd.dark.min.css │ ├── antd.dark.min.css.map │ ├── antd.min.css │ └── antd.min.css.map │ ├── image │ ├── report_statistics_preview.png │ └── white_list_preview.png │ └── user-agreement.html ├── scripts ├── getGitVersion.js └── moveAntdCss.js ├── src ├── App.test.tsx ├── App.tsx ├── api │ ├── OperationRecord │ │ ├── index.d.ts │ │ └── index.ts │ ├── Service.base.ts │ ├── SqlManage │ │ ├── index.d.ts │ │ ├── index.enum.ts │ │ └── index.ts │ ├── audit_plan │ │ ├── index.d.ts │ │ └── index.ts │ ├── audit_whitelist │ │ ├── index.d.ts │ │ └── index.ts │ ├── common.d.ts │ ├── common.enum.ts │ ├── companyNotice │ │ ├── index.d.ts │ │ └── index.ts │ ├── configuration │ │ ├── index.d.ts │ │ └── index.ts │ ├── dashboard │ │ ├── index.d.ts │ │ └── index.ts │ ├── global │ │ ├── index.d.ts │ │ └── index.ts │ ├── instance │ │ ├── index.d.ts │ │ ├── index.enum.ts │ │ └── index.ts │ ├── management_permission │ │ ├── index.d.ts │ │ └── index.ts │ ├── oauth2 │ │ ├── index.d.ts │ │ └── index.ts │ ├── operation │ │ ├── index.d.ts │ │ └── index.ts │ ├── project │ │ ├── index.d.ts │ │ ├── index.enum.ts │ │ └── index.ts │ ├── role │ │ ├── index.d.ts │ │ └── index.ts │ ├── rule_template │ │ ├── index.d.ts │ │ └── index.ts │ ├── sql_analysis │ │ ├── index.d.ts │ │ └── index.ts │ ├── sql_audit │ │ ├── index.d.ts │ │ └── index.ts │ ├── sql_audit_record │ │ ├── index.d.ts │ │ ├── index.enum.ts │ │ └── index.ts │ ├── sql_query │ │ ├── index.d.ts │ │ └── index.ts │ ├── statistic │ │ ├── index.d.ts │ │ └── index.ts │ ├── sync_instance │ │ ├── index.d.ts │ │ └── index.ts │ ├── task │ │ ├── index.d.ts │ │ ├── index.enum.ts │ │ └── index.ts │ ├── user │ │ ├── index.d.ts │ │ └── index.ts │ ├── user_group │ │ ├── index.d.ts │ │ └── index.ts │ └── workflow │ │ ├── index.d.ts │ │ ├── index.enum.ts │ │ └── index.ts ├── assets │ ├── font │ │ └── SourceHanSansSC-Bold.woff2 │ └── img │ │ ├── en-us.svg │ │ ├── login-left-bg.png │ │ ├── logo.png │ │ ├── moon.svg │ │ ├── sun.svg │ │ └── zh-cn.svg ├── components │ ├── AuditResultErrorMessage │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.less │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── index.type.ts │ ├── BackButton │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── BackendForm │ │ ├── BackendForm.test.tsx │ │ ├── BackendForm.tsx │ │ ├── __snapshots__ │ │ │ └── BackendForm.test.tsx.snap │ │ ├── index.ts │ │ ├── useAsyncParams.test.tsx │ │ └── useAsyncParams.ts │ ├── CopyIcon │ │ ├── CopyIcon.test.tsx │ │ ├── CopyIcon.tsx │ │ ├── __snapshots__ │ │ │ └── CopyIcon.test.tsx.snap │ │ ├── index.less │ │ └── index.tsx │ ├── CronInput │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.data.ts │ │ ├── index.less │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── index.type.ts │ ├── DatabaseTypeLogo │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.less │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── EditText │ │ ├── EditText.test.tsx │ │ ├── EditText.tsx │ │ ├── __snapshots__ │ │ │ └── EditText.test.tsx.snap │ │ └── index.tsx │ ├── EmptyBox │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── EnterpriseFeatureDisplay │ │ ├── EnterpriseFeatureDisplay.test.tsx │ │ ├── EnterpriseFeatureDisplay.tsx │ │ ├── __snapshots__ │ │ │ └── EnterpriseFeatureDisplay.test.tsx.snap │ │ └── index.tsx │ ├── FooterButtonWrapper │ │ ├── FooterButtonWrapper.test.tsx │ │ ├── FooterButtonWrapper.tsx │ │ ├── __snapshots__ │ │ │ └── FooterButtonWrapper.test.tsx.snap │ │ ├── index.less │ │ └── index.tsx │ ├── HeaderProgress │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── IconTipsLabel │ │ ├── IconTipsLabel.test.tsx │ │ ├── IconTipsLabel.tsx │ │ ├── __snapshots__ │ │ │ └── IconTipsLabel.test.tsx.snap │ │ └── index.ts │ ├── LanguageSelect │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── Link │ │ ├── Link.tsx │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── Nav │ │ ├── Header │ │ │ ├── Component │ │ │ │ ├── CompanyNoticeTrigger.tsx │ │ │ │ ├── HeaderMenu.tsx │ │ │ │ ├── Logo.tsx │ │ │ │ ├── MoreAction.tsx │ │ │ │ ├── OperationRecordNavigation.tsx │ │ │ │ ├── ProjectNavigation.tsx │ │ │ │ ├── UserNavigation.tsx │ │ │ │ └── __test__ │ │ │ │ │ ├── HeaderMenu.test.tsx │ │ │ │ │ ├── Logo.test.tsx │ │ │ │ │ ├── MoreAction.test.tsx │ │ │ │ │ ├── OperationRecordNavigation.test.tsx │ │ │ │ │ ├── ProjectNavigation.test.tsx │ │ │ │ │ ├── UserNavigation.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ ├── HeaderMenu.test.tsx.snap │ │ │ │ │ ├── Logo.test.tsx.snap │ │ │ │ │ ├── MoreAction.test.tsx.snap │ │ │ │ │ ├── OperationRecordNavigation.test.tsx.snap │ │ │ │ │ ├── ProjectNavigation.test.tsx.snap │ │ │ │ │ └── UserNavigation.test.tsx.snap │ │ │ ├── Modal │ │ │ │ ├── CompanyNoticeModal │ │ │ │ │ └── index.tsx │ │ │ │ ├── VersionModal │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ └── index.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.less │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── OrderStatusTag │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── index.type.ts │ ├── RuleList │ │ ├── RuleLevelIcon │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ ├── index.type.ts │ │ └── useSyncRuleListTab │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ └── TestDatabaseConnectButton │ │ ├── __snapshots__ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── index.type.ts ├── data │ ├── EmitterKey.ts │ ├── ModalName.ts │ ├── StorageKey.ts │ └── common.ts ├── hooks │ ├── useAuditPlanTypes │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useBack │ │ ├── index.test.ts │ │ └── index.ts │ ├── useBackendTable │ │ ├── __snapshots__ │ │ │ └── useBackendTable.test.tsx.snap │ │ ├── index.ts │ │ ├── useBackendTable.test.tsx │ │ └── useBackendTable.tsx │ ├── useChangeTheme │ │ ├── index.test.ts │ │ └── index.ts │ ├── useCron │ │ ├── cron.tool.test.ts │ │ ├── cron.tool.ts │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── index.type.ts │ ├── useCurrentUser │ │ ├── index.test.data.ts │ │ ├── index.test.ts │ │ └── index.ts │ ├── useDatabaseType │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.data.ts │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useGlobalRuleTemplate │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useInstance │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useInstanceSchema │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useLanguage │ │ ├── index.test.tsx │ │ └── index.ts │ ├── useManagerPermission │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ ├── index.ts │ │ └── useManagerPermission.tsx │ ├── useMember │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useMonacoEditor │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.data.ts │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ ├── index.type.ts │ │ └── regexLanguage.ts │ ├── useNavigate │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useOperation │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── useOperation.tsx │ ├── useOperationActions │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useOperationTypeName │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useProject │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useRole │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useRuleTemplate │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useRuleType │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useSQLAuditRecordTag │ │ └── index.tsx │ ├── useStaticStatus │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.data.ts │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── index.type.ts │ ├── useTable │ │ ├── index.test.ts │ │ ├── index.ts │ │ └── index.type.ts │ ├── useTaskSource │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useUserGroup │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── useUserInfo │ │ ├── index.test.ts │ │ └── index.ts │ └── useUsername │ │ ├── __snapshots__ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx ├── index.less ├── index.tsx ├── locale │ ├── en-US │ │ ├── account.ts │ │ ├── audit.ts │ │ ├── auditPlan.ts │ │ ├── common.ts │ │ ├── dashboard.ts │ │ ├── dataSource.ts │ │ ├── index.ts │ │ ├── login.ts │ │ ├── menu.ts │ │ ├── order.ts │ │ ├── projectManage.ts │ │ ├── reportStatistics.ts │ │ ├── rule.ts │ │ ├── ruleTemplate.ts │ │ ├── syncDataSource.ts │ │ ├── system.ts │ │ ├── user.ts │ │ └── whitelist.ts │ ├── index.ts │ └── zh-CN │ │ ├── account.ts │ │ ├── audit.ts │ │ ├── auditPlan.ts │ │ ├── common.ts │ │ ├── customRule.ts │ │ ├── dashboard.ts │ │ ├── dataSource.ts │ │ ├── index.ts │ │ ├── login.ts │ │ ├── member.ts │ │ ├── menu.ts │ │ ├── operationRecord.ts │ │ ├── order.ts │ │ ├── projectManage.ts │ │ ├── reportStatistics.ts │ │ ├── role.ts │ │ ├── rule.ts │ │ ├── ruleKnowledge.ts │ │ ├── ruleManager.ts │ │ ├── ruleTemplate.ts │ │ ├── sqlAnalyze.ts │ │ ├── sqlAudit.ts │ │ ├── sqlManagement.ts │ │ ├── sqlQuery.ts │ │ ├── syncDataSource.ts │ │ ├── system.ts │ │ ├── user.ts │ │ ├── userGroup.ts │ │ ├── whitelist.ts │ │ └── workflowTemplate.ts ├── page │ ├── Account │ │ ├── Modal │ │ │ └── ModifyPassword │ │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ ├── UserEmail │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── hooks │ │ │ │ └── useInputChange.tsx │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── UserPhone │ │ │ ├── Phone.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── Wechat │ │ │ ├── Wechat.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── AuditPlan │ │ ├── CreatePlan │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── PlanDetail │ │ │ ├── Detail │ │ │ │ ├── Record │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Report │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── index.type.tsx │ │ │ │ │ └── tableHeader.tsx │ │ │ │ ├── SqlPool │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ └── index.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── __testData__ │ │ │ │ └── index.ts │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── PlanForm │ │ │ ├── AuditTaskType │ │ │ │ ├── AuditTaskType.tsx │ │ │ │ └── index.tsx │ │ │ ├── DataSource │ │ │ │ ├── DataSource.tsx │ │ │ │ └── index.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── __testData__ │ │ │ │ └── auditMeta.ts │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── PlanList │ │ │ ├── Modal │ │ │ │ ├── SubscribeNotice │ │ │ │ │ ├── WebhooksTemplateHelp.tsx │ │ │ │ │ ├── index.data.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── index.type.ts │ │ │ │ │ └── tests │ │ │ │ │ │ ├── SubscribeNotice.test.tsx │ │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── SubscribeNotice.test.tsx.snap │ │ │ │ │ │ └── __testData__ │ │ │ │ │ │ └── index.ts │ │ │ │ └── index.tsx │ │ │ ├── PlanListFilterForm │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── __testData__ │ │ │ │ └── index.ts │ │ │ ├── component │ │ │ │ └── TokenText │ │ │ │ │ └── index.tsx │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── tableColumn.tsx │ │ ├── UpdatePlan │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── BindUser │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── index.type.ts │ ├── CustomRule │ │ ├── CreateCustomRule │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── CustomRuleForm │ │ │ ├── BaseInfoForm.tsx │ │ │ ├── CustomRuleForm.tsx │ │ │ ├── EditRuleScript.tsx │ │ │ ├── __tests__ │ │ │ │ ├── BaseInfoForm.test.tsx │ │ │ │ ├── EditRuleScript.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ ├── BaseInfoForm.test.tsx.snap │ │ │ │ │ └── EditRuleScript.test.tsx.snap │ │ │ └── index.tsx │ │ ├── CustomRuleList │ │ │ ├── CustomRuleList.tsx │ │ │ ├── FilterFormAndCreateButton.tsx │ │ │ ├── __tests__ │ │ │ │ ├── CustomRuleList.test.tsx │ │ │ │ ├── FilterFormAndCreateButton.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ ├── CustomRuleList.test.tsx.snap │ │ │ │ │ └── FilterFormAndCreateButton.test.tsx.snap │ │ │ └── index.tsx │ │ ├── UpdateCustomRule │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── __mockApi__ │ │ │ ├── data.ts │ │ │ └── index.ts │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── DataSource │ │ ├── AddDataSource │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── DataSourceForm │ │ │ ├── DatabaseFormItem │ │ │ │ ├── index.test.tsx │ │ │ │ └── index.tsx │ │ │ ├── MaintenanceTimePicker │ │ │ │ ├── MaintenanceTimePicker.less │ │ │ │ ├── MaintenanceTimePicker.test.tsx │ │ │ │ ├── MaintenanceTimePicker.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── MaintenanceTimePicker.test.tsx.snap │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── DataSourceList │ │ │ ├── DataSourceListFilterForm │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── columns.tsx │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── UpdateDataSource │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── __testData__ │ │ │ └── index.tsx │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── tool │ │ │ ├── index.test.ts │ │ │ └── index.ts │ ├── GlobalRuleTemplate │ │ ├── CreateRuleTemplate │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── ImportRuleTemplate │ │ │ ├── ImportRuleTemplate.tsx │ │ │ ├── __test__ │ │ │ │ ├── ImportRuleTemplate.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── ImportRuleTemplate.test.tsx.snap │ │ │ └── index.tsx │ │ ├── RuleTemplateForm │ │ │ ├── BaseInfoForm │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ │ ├── RuleSelect │ │ │ │ ├── RuleManagerModal │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── __testData__ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.less │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── index.type.ts │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── RuleTemplateList │ │ │ ├── Modal │ │ │ │ ├── CloneRuleTemplate │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── index.type.ts │ │ │ │ └── index.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── column.tsx │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── UpdateRuleTemplate │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── __testData__ │ │ │ └── index.ts │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── Home │ │ ├── CommonTable │ │ │ ├── Table.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── column.tsx │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── DBAPanel │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── DEVPanel │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── RecentlyOrderPanel │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── column.tsx │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.less │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── useDashboardRequest.ts │ ├── Login │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.less │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── Member │ │ ├── Common │ │ │ ├── RoleSelector.tsx │ │ │ ├── __test__ │ │ │ │ ├── RoleSelector.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── RoleSelector.test.tsx.snap │ │ │ │ │ └── renderRolesInfo.test.tsx.snap │ │ │ │ └── renderRolesInfo.test.tsx │ │ │ └── renderRolesInfo.tsx │ │ ├── MemberGroupList │ │ │ ├── FilterForm.tsx │ │ │ ├── __test__ │ │ │ │ ├── FilterForm.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── FilterForm.test.tsx.snap │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ └── utils.ts │ │ │ ├── column.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── MemberList │ │ │ ├── FilterForm.tsx │ │ │ ├── __test__ │ │ │ │ ├── FilterForm.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── FilterForm.test.tsx.snap │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ └── utils.ts │ │ │ ├── column.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── Modal │ │ │ ├── AddMember.tsx │ │ │ ├── AddMemberGroup.tsx │ │ │ ├── MemberForm.tsx │ │ │ ├── MemberGroupForm.tsx │ │ │ ├── UpdateMember.tsx │ │ │ ├── UpdateMemberGroup.tsx │ │ │ ├── __test__ │ │ │ │ ├── AddMember.test.tsx │ │ │ │ ├── AddMemberGroup.test.tsx │ │ │ │ ├── MemberForm.test.tsx │ │ │ │ ├── MemberGroupForm.test.tsx │ │ │ │ ├── UpdateMember.test.tsx │ │ │ │ ├── UpdateMemberGroup.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── AddMember.test.tsx.snap │ │ │ │ │ ├── AddMemberGroup.test.tsx.snap │ │ │ │ │ ├── MemberForm.test.tsx.snap │ │ │ │ │ ├── MemberGroupForm.test.tsx.snap │ │ │ │ │ ├── UpdateMember.test.tsx.snap │ │ │ │ │ └── UpdateMemberGroup.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ └── utils.ts │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── OperationRecord │ │ ├── OperationRecordList │ │ │ ├── FilterForm.tsx │ │ │ ├── List.tsx │ │ │ ├── __test__ │ │ │ │ ├── FilterForm.test.tsx │ │ │ │ ├── List.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ ├── FilterForm.test.tsx.snap │ │ │ │ │ └── List.test.tsx.snap │ │ │ ├── column.tsx │ │ │ └── index.tsx │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── Order │ │ ├── AuditResult │ │ │ ├── AuditResultCollection.tsx │ │ │ ├── AuditResultFilterForm.tsx │ │ │ ├── AuditResultInfo │ │ │ │ ├── index.less │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ │ ├── InstanceTasksStatus.tsx │ │ │ ├── RenderExecuteSql.tsx │ │ │ ├── ScheduleTimeModal.tsx │ │ │ ├── __test__ │ │ │ │ ├── AuditResultCollection.test.tsx │ │ │ │ ├── AuditResultInfo.test.tsx │ │ │ │ ├── InstanceTasksStatus.test.tsx │ │ │ │ ├── RenderExecuteSql.test.tsx │ │ │ │ ├── ScheduleTimeModal.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── AuditResultCollection.test.tsx.snap │ │ │ │ │ ├── AuditResultInfo.test.tsx.snap │ │ │ │ │ ├── InstanceTasksStatus.test.tsx.snap │ │ │ │ │ ├── RenderExecuteSql.test.tsx.snap │ │ │ │ │ ├── ScheduleTimeModal.test.tsx.snap │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── column.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── Create │ │ │ ├── SqlInfoForm │ │ │ │ ├── DatabaseInfo.tsx │ │ │ │ ├── __test__ │ │ │ │ │ ├── DatabaseInfo.test.tsx │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ ├── DatabaseInfo.test.tsx.snap │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── common.ts │ │ │ │ │ └── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.enum.ts │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── Detail │ │ │ ├── Modal │ │ │ │ ├── ModifySqlModal │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── index.type.ts │ │ │ │ └── OrderHistory │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── index.type.ts │ │ │ ├── OrderSteps │ │ │ │ ├── __test__ │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ ├── index.test.tsx.snap │ │ │ │ │ │ └── useGenerateOrderStepInfo.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── testData.ts │ │ │ │ │ ├── useGenerateOrderStepInfo.test.tsx │ │ │ │ │ └── utils.test.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── index.type.ts │ │ │ │ ├── useGenerateOrderStepInfo.tsx │ │ │ │ └── utils.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── __testData__ │ │ │ │ └── index.tsx │ │ │ ├── hooks │ │ │ │ ├── __test__ │ │ │ │ │ ├── useGenerateOrderStepsProps.test.ts │ │ │ │ │ ├── useInitDataWithRequest.test.ts │ │ │ │ │ └── useModifySql.test.tsx │ │ │ │ ├── useGenerateOrderStepsProps.ts │ │ │ │ ├── useInitDataWithRequest.ts │ │ │ │ └── useModifySql.ts │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── List │ │ │ ├── OrderListFilterForm │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── column.tsx │ │ │ ├── index.data.ts │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── SqlStatementFormTabs │ │ │ ├── SqlStatementForm.tsx │ │ │ ├── SqlStatementFormTabs.tsx │ │ │ ├── __test__ │ │ │ │ ├── SqlStatementForm.test.tsx │ │ │ │ ├── SqlStatementFormTabs.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── SqlStatementForm.test.tsx.snap │ │ │ │ │ └── SqlStatementFormTabs.test.tsx.snap │ │ │ │ └── test.data.ts │ │ │ └── index.ts │ │ └── hooks │ │ │ ├── __test__ │ │ │ ├── test.data.ts │ │ │ ├── useAllowAuditLevel.test.tsx │ │ │ └── useAuditOrder.test.ts │ │ │ ├── useAllowAuditLevel.ts │ │ │ └── useAuditOrder.tsx │ ├── ProjectManage │ │ ├── Modal │ │ │ ├── CreateProject.tsx │ │ │ ├── ProjectForm │ │ │ │ ├── ProjectForm.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ └── index.ts │ │ │ ├── UpdateProject.tsx │ │ │ ├── __test__ │ │ │ │ ├── CreateProject.test.tsx │ │ │ │ ├── UpdateProject.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── CreateProject.test.tsx.snap │ │ │ │ │ └── UpdateProject.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ └── index.tsx │ │ ├── ProjectDetail │ │ │ ├── Layout │ │ │ │ ├── Layout.tsx │ │ │ │ ├── __test__ │ │ │ │ │ ├── Layout.test.tsx │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ ├── Layout.test.tsx.snap │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ └── index.test.tsx │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── ProjectDetail.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── ProjectList │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── column.tsx │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── ProjectOverview │ │ │ ├── Chats │ │ │ │ ├── CommonGauge │ │ │ │ │ ├── index.data.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── registerShape.ts │ │ │ │ ├── __test__ │ │ │ │ │ ├── CommonGauge.test.tsx │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ └── CommonGauge.test.tsx.snap │ │ │ │ └── index.tsx │ │ │ ├── Panel │ │ │ │ ├── ApprovalProcess.tsx │ │ │ │ ├── AuditPlanClassification.tsx │ │ │ │ ├── AuditPlanRisk.tsx │ │ │ │ ├── DataSourceCount.tsx │ │ │ │ ├── MemberInfo.tsx │ │ │ │ ├── OrderClassification.tsx │ │ │ │ ├── OrderRisk.tsx │ │ │ │ ├── PanelWrapper.tsx │ │ │ │ ├── ProjectScore.tsx │ │ │ │ ├── SqlCount.tsx │ │ │ │ ├── __test__ │ │ │ │ │ ├── ApprovalProcess.test.tsx │ │ │ │ │ ├── AuditPlanClassification.test.tsx │ │ │ │ │ ├── AuditPlanRisk.test.tsx │ │ │ │ │ ├── DataSourceCount.test.tsx │ │ │ │ │ ├── MemberInfo.test.tsx │ │ │ │ │ ├── OrderClassification.test.tsx │ │ │ │ │ ├── OrderRisk.test.tsx │ │ │ │ │ ├── ProjectScore.test.tsx │ │ │ │ │ ├── SQLCount.test.tsx │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ ├── ApprovalProcess.test.tsx.snap │ │ │ │ │ │ ├── AuditPlanClassification.test.tsx.snap │ │ │ │ │ │ ├── AuditPlanRisk.test.tsx.snap │ │ │ │ │ │ ├── DataSourceCount.test.tsx.snap │ │ │ │ │ │ ├── MemberInfo.test.tsx.snap │ │ │ │ │ │ ├── OrderClassification.test.tsx.snap │ │ │ │ │ │ ├── OrderRisk.test.tsx.snap │ │ │ │ │ │ ├── ProjectScore.test.tsx.snap │ │ │ │ │ │ └── SQLCount.test.tsx.snap │ │ │ │ │ ├── index.data.ts │ │ │ │ │ ├── mockApi.ts │ │ │ │ │ └── usePanelCommonRequest.test.ts │ │ │ │ ├── index.ts │ │ │ │ └── usePanelCommonRequest.ts │ │ │ ├── ProjectInfoBox.tsx │ │ │ ├── __test__ │ │ │ │ ├── ProjectInfoBox.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── ProjectInfoBox.test.tsx.snap │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ ├── index.data.tsx │ │ │ ├── index.enum.ts │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ └── __test__ │ │ │ └── utils.ts │ ├── ReportStatistics │ │ ├── Charts │ │ │ ├── CommonLine.tsx │ │ │ ├── CommonPie.tsx │ │ │ ├── CommonRadialBar.tsx │ │ │ ├── __test__ │ │ │ │ ├── CommonLine.test.tsx │ │ │ │ ├── CommonPie.test.tsx │ │ │ │ ├── CommonRadialBar.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── CommonLine.test.tsx.snap │ │ │ │ │ ├── CommonPie.test.tsx.snap │ │ │ │ │ └── CommonRadialBar.test.tsx.snap │ │ │ │ └── index.data.ts │ │ │ └── index.ts │ │ ├── Panel │ │ │ ├── DiffUserOrderRejectedPercent.tsx │ │ │ ├── InstanceProportionWithDbType.tsx │ │ │ ├── LicenseUsage.tsx │ │ │ ├── OrderAverageExecuteTimeTopN.tsx │ │ │ ├── OrderAverageReviewTime.tsx │ │ │ ├── OrderPassPercent.tsx │ │ │ ├── OrderQuantityTrend.tsx │ │ │ ├── OrderQuantityWithDbType.tsx │ │ │ ├── OrderStatus.tsx │ │ │ ├── OrderTotalNumbers.tsx │ │ │ ├── PanelWrapper.tsx │ │ │ ├── SqlExecFailedTopN.tsx │ │ │ ├── __test__ │ │ │ │ ├── DiffUserOrderRejectedPercent.test.tsx │ │ │ │ ├── InstanceProportionWithDbType.test.tsx │ │ │ │ ├── LicenseUsage.test.tsx │ │ │ │ ├── OrderAverageExecuteTimeTopN.test.tsx │ │ │ │ ├── OrderAverageReviewTime.test.tsx │ │ │ │ ├── OrderPassPercent.test.tsx │ │ │ │ ├── OrderQuantityTrend.test.tsx │ │ │ │ ├── OrderQuantityWithDbType.test.tsx │ │ │ │ ├── OrderStatus.test.tsx │ │ │ │ ├── OrderTotalNumbers.test.tsx │ │ │ │ ├── PanelWrapper.test.tsx │ │ │ │ ├── SqlExecFailedTopN.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── DiffUserOrderRejectedPercent.test.tsx.snap │ │ │ │ │ ├── InstanceProportionWithDbType.test.tsx.snap │ │ │ │ │ ├── LicenseUsage.test.tsx.snap │ │ │ │ │ ├── OrderAverageExecuteTimeTopN.test.tsx.snap │ │ │ │ │ ├── OrderAverageReviewTime.test.tsx.snap │ │ │ │ │ ├── OrderPassPercent.test.tsx.snap │ │ │ │ │ ├── OrderQuantityTrend.test.tsx.snap │ │ │ │ │ ├── OrderQuantityWithDbType.test.tsx.snap │ │ │ │ │ ├── OrderStatus.test.tsx.snap │ │ │ │ │ ├── OrderTotalNumbers.test.tsx.snap │ │ │ │ │ ├── PanelWrapper.test.tsx.snap │ │ │ │ │ └── SqlExecFailedTopN.test.tsx.snap │ │ │ │ ├── mockRequestData.ts │ │ │ │ └── usePanelCommonRequest.test.ts │ │ │ ├── index.tsx │ │ │ └── usePanelCommonRequest.ts │ │ ├── __test__ │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ └── index.test.tsx │ │ ├── index.data.tsx │ │ ├── index.enum.ts │ │ ├── index.less │ │ ├── index.tsx │ │ └── index.type.ts │ ├── Rule │ │ ├── __testData__ │ │ │ └── index.ts │ │ ├── __test__ │ │ │ ├── __snapshots__ │ │ │ │ ├── index.test.tsx.snap │ │ │ │ └── useRuleFilterForm.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── useRuleFilterForm.test.tsx │ │ │ └── utils.ts │ │ ├── index.tsx │ │ └── useRuleFilterForm.tsx │ ├── RuleKnowledge │ │ ├── RuleUnderstand │ │ │ ├── EditKnowledgeContent.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ └── index.tsx │ ├── RuleManager │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── RuleTemplate │ │ ├── CreateRuleTemplate │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── ImportRuleTemplate │ │ │ ├── ImportRuleTemplate.tsx │ │ │ ├── __test__ │ │ │ │ ├── ImportRuleTemplate.test.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── ImportRuleTemplate.test.tsx.snap │ │ │ └── index.tsx │ │ ├── RuleTemplateForm │ │ │ ├── BaseInfoForm │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ │ ├── RuleSelect │ │ │ │ ├── RuleManagerModal │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── __testData__ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.less │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── index.type.ts │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── index.type.ts │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── RuleTemplateList │ │ │ ├── Modal │ │ │ │ ├── CloneRuleTemplate │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── index.type.ts │ │ │ │ └── index.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── column.tsx │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── UpdateRuleTemplate │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── __testData__ │ │ │ └── index.ts │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── SQLManagement │ │ ├── SQLPanel │ │ │ ├── AssignMember.tsx │ │ │ ├── FilterForm.tsx │ │ │ ├── SQLStatistics.tsx │ │ │ ├── UpdateSQLStatus.tsx │ │ │ ├── column.tsx │ │ │ ├── hooks │ │ │ │ ├── useRuleTips.tsx │ │ │ │ └── useStaticStatus.tsx │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ └── index.tsx │ ├── SqlAnalyze │ │ ├── AuditPlan │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── Order │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── SqlAnalyze │ │ │ ├── SqlAnalyze.test.tsx │ │ │ ├── SqlAnalyze.tsx │ │ │ ├── __snapshots__ │ │ │ │ ├── SqlAnalyze.test.tsx.snap │ │ │ │ ├── useSQLExecPlan.test.tsx.snap │ │ │ │ └── useTableSchema.test.tsx.snap │ │ │ ├── index.tsx │ │ │ ├── useSQLExecPlan.test.tsx │ │ │ ├── useSQLExecPlan.tsx │ │ │ ├── useTableSchema.test.tsx │ │ │ └── useTableSchema.tsx │ │ ├── SqlManage │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ └── __testData__ │ │ │ └── index.ts │ ├── SqlAuditRecord │ │ ├── Create │ │ │ ├── BaseInfoForm.tsx │ │ │ ├── SQLInfoForm.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── Detail │ │ │ └── index.tsx │ │ └── List │ │ │ ├── CustomTags.tsx │ │ │ ├── FilterForm.tsx │ │ │ ├── column.tsx │ │ │ ├── index.data.ts │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ ├── SqlQuery │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── __testData__ │ │ │ └── index.ts │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── index.type.ts │ ├── SyncDataSource │ │ ├── AddSyncTask │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── SyncTaskForm │ │ │ ├── SyncTaskForm.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── SyncTaskList │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── column.tsx │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── UpdateSyncTask │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── System │ │ ├── DingTalkSetting │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── GlobalSetting │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── LDAPSetting │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── LarkAuditSetting │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── LarkSetting │ │ │ ├── LarkSetting.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── License │ │ │ ├── Modal │ │ │ │ ├── ImportLicense.test.tsx │ │ │ │ ├── ImportLicense.tsx │ │ │ │ └── __snapshots__ │ │ │ │ │ └── ImportLicense.test.tsx.snap │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── __testData__ │ │ │ │ └── index.ts │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── Oauth │ │ │ ├── Oauth.tsx │ │ │ ├── Ouath.test.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── Ouath.test.tsx.snap │ │ │ ├── __testData__ │ │ │ │ └── index.ts │ │ │ └── index.type.ts │ │ ├── PersonalizeSetting │ │ │ ├── CustomTitle.tsx │ │ │ ├── UploadLogo.tsx │ │ │ ├── __test__ │ │ │ │ ├── CustomTitle.test.tsx │ │ │ │ ├── UploadLogo.test.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── CustomTitle.test.tsx.snap │ │ │ │ │ ├── UploadLogo.test.tsx.snap │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ └── index.test.tsx │ │ │ └── index.tsx │ │ ├── SMTPSetting │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── WebhookSetting │ │ │ ├── Webhook.test.tsx │ │ │ ├── Webhook.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Webhook.test.tsx.snap │ │ ├── Wechat │ │ │ ├── Wechat.test.tsx │ │ │ ├── Wechat.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Wechat.test.tsx.snap │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── useConditionalConfig.test.tsx.snap │ │ │ │ └── useConditionalConfig.test.tsx │ │ │ └── useConditionalConfig.tsx │ │ ├── index.test.tsx │ │ └── index.tsx │ ├── UserCenter │ │ ├── Common │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.ts.snap │ │ │ ├── generateTag.tsx │ │ │ └── index.test.ts │ │ ├── Role │ │ │ ├── Modal │ │ │ │ ├── AddRole │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── RoleForm │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── index.type.ts │ │ │ │ ├── UpdateRole │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ └── RoleList │ │ │ │ ├── RoleListFilterForm │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── __testData__ │ │ │ │ └── index.ts │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── index.type.ts │ │ │ │ └── tableColumn.tsx │ │ ├── User │ │ │ ├── Modal │ │ │ │ ├── AddUser │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── ModifyUserPassword │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── UpdateUser │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── UserForm │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── index.type.ts │ │ │ │ └── index.tsx │ │ │ └── UserList │ │ │ │ ├── UserListFilterForm │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── index.test.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── __testData__ │ │ │ │ └── index.ts │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── index.type.ts │ │ │ │ └── tableHeader.tsx │ │ ├── UserCenter.tsx │ │ ├── UserGroup │ │ │ ├── Modal │ │ │ │ ├── AddUserGroup │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── UpdateUserGroup │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── UserGroupForm │ │ │ │ │ ├── UserGroupForm.test.tsx │ │ │ │ │ ├── UserGroupForm.tsx │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── UserGroupForm.test.tsx.snap │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── useUserGroupFormOption.test.tsx │ │ │ │ │ └── useUserGroupFormOption.tsx │ │ │ │ └── index.tsx │ │ │ └── UserGroupList │ │ │ │ ├── TableFilterForm │ │ │ │ ├── TableFilterForm.test.tsx │ │ │ │ ├── TableFilterForm.tsx │ │ │ │ └── index.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ │ ├── __testData__ │ │ │ │ └── index.tsx │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── tableHeader.tsx │ │ ├── UserManageModal.tsx │ │ ├── __test__ │ │ │ ├── UserCenter.test.tsx │ │ │ ├── UserManageModal.test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── UserCenter.test.tsx.snap │ │ └── index.tsx │ ├── Whitelist │ │ ├── WhitelistForm │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── WhitelistList │ │ │ ├── Modal │ │ │ │ ├── AddWhitelist │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── UpdateWhitelist │ │ │ │ │ ├── __snapshots__ │ │ │ │ │ │ └── index.test.tsx.snap │ │ │ │ │ ├── index.test.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── column.tsx │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── __testData__ │ │ │ └── index.ts │ │ ├── index.test.tsx │ │ └── index.tsx │ └── WorkflowTemplate │ │ ├── UpdateWorkflowTemplate │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ │ ├── WorkflowTemplateDetail │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ └── index.tsx │ │ ├── WorkflowTemplateForm │ │ ├── BaseForm │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── ProgressConfig │ │ │ ├── __snapshots__ │ │ │ │ └── index.test.tsx.snap │ │ │ ├── index.test.tsx │ │ │ ├── index.tsx │ │ │ └── index.type.ts │ │ ├── __snapshots__ │ │ │ └── index.test.tsx.snap │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── index.type.ts │ │ ├── __snapshots__ │ │ └── index.test.tsx.snap │ │ ├── __testData__ │ │ └── index.ts │ │ ├── index.test.tsx │ │ └── index.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── router │ ├── RouterAuth.tsx │ ├── __test__ │ │ └── RouterAuth.test.tsx │ └── config.tsx ├── scripts │ └── version.ts ├── setupTests.ts ├── store │ ├── auditPlan │ │ ├── index.test.ts │ │ └── index.tsx │ ├── common │ │ ├── index.test.ts │ │ └── index.ts │ ├── globalRuleTemplate │ │ ├── index.test.ts │ │ └── index.tsx │ ├── index.ts │ ├── locale │ │ ├── index.test.ts │ │ └── index.ts │ ├── member │ │ ├── index.test.ts │ │ └── index.ts │ ├── nav │ │ └── index.ts │ ├── projectManage │ │ ├── index.test.ts │ │ └── index.ts │ ├── reportStatistics │ │ ├── index.test.ts │ │ └── index.tsx │ ├── ruleTemplate │ │ ├── index.test.ts │ │ └── index.tsx │ ├── system │ │ └── index.ts │ ├── user │ │ ├── index.test.ts │ │ └── index.ts │ ├── userManage │ │ ├── index.test.ts │ │ └── index.ts │ └── whitelist │ │ ├── index.test.ts │ │ └── index.ts ├── styles │ ├── _variable.less │ ├── component.less │ └── tool.less ├── testUtils │ ├── customQuery.ts │ ├── customRender.tsx │ ├── mockAntDesignPlots.jsx │ ├── mockEditor.jsx │ ├── mockRedux.tsx │ ├── mockRequest.ts │ └── mockStyle.ts ├── theme │ ├── dark.ts │ ├── index.ts │ └── light.ts ├── types │ ├── common.type.ts │ ├── react-i18next.d.ts │ └── router.type.ts └── utils │ ├── Api.ts │ ├── Common.ts │ ├── Copy.ts │ ├── Download.ts │ ├── EventEmitter.ts │ ├── FormRule.ts │ ├── FormatterSQL.ts │ ├── HighlightCode.ts │ ├── LocalStorageWrapper.ts │ ├── Math.ts │ └── test │ ├── Copy.test.ts │ ├── Download.test.ts │ ├── EventEmitter.test.ts │ ├── FormRule.test.ts │ ├── FormatSQL.test.ts │ ├── __snapshots__ │ └── FormatSQL.test.ts.snap │ ├── api.test.ts │ ├── common.test.ts │ └── math.test.ts ├── tsconfig.json └── yarn.lock /.env.test: -------------------------------------------------------------------------------- 1 | TZ=Asia/Shanghai -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["react-app", "react-app/jest"], 3 | "rules": { 4 | "no-console": "warn", 5 | "testing-library/render-result-naming-convention": 0, 6 | "testing-library/no-unnecessary-act": 0, 7 | "testing-library/no-node-access": 0 8 | }, 9 | "ignorePatterns": "src/api/**/*.ts" 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | name: CI 3 | # Controls when the workflow will run 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the main branch 6 | pull_request: 7 | branches: [main, release*, 'temporary/**'] 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "build" 14 | test-coverage: 15 | # The type of runner that the job will run on 16 | runs-on: ubuntu-latest 17 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 18 | # Steps represent a sequence of tasks that will be executed as part of the job 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: artiomtr/jest-coverage-report-action@v2 22 | with: 23 | github-token: ${{ secrets.GITHUB_TOKEN }} 24 | test-script: yarn test --coverage --watchAll=false --ci --silent --json --testLocationInResults --outputFile="report.json" 25 | package-manager: yarn 26 | -------------------------------------------------------------------------------- /.github/workflows/pr-release-2-main.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: 4 | - release* 5 | types: ["closed"] 6 | 7 | jobs: 8 | cherry_pick_release_2_main: 9 | if: github.event.pull_request.merged == true 10 | runs-on: ubuntu-latest 11 | name: Cherry pick into main 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: Cherry pick into main 18 | uses: Nathanmalnoury/gh-backport-action@master 19 | with: 20 | pr_branch: 'main' 21 | github_token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .vscode 8 | .eslintcache 9 | /scripts/api-modeling/node_modules 10 | /scripts/api-modeling/dist 11 | /scripts/api-modeling/yarn.lock 12 | /scripts/api-modeling/package-lock.json 13 | 14 | # testing 15 | /coverage 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | .env.local 23 | .env.development.local 24 | .env.test.local 25 | .env.production.local 26 | 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | unit-test: 2 | script: 3 | - make test 4 | only: 5 | - merge_request 6 | tags: 7 | - docker 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | docker pull docker-registry:5000/actiontech/sqle-ui-unit-test && \ 3 | docker run --rm -v ${shell pwd}:/app docker-registry:5000/actiontech/sqle-ui-unit-test 4 | -------------------------------------------------------------------------------- /QR_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actiontech/sqle-ui/e0259511cddb59fd3ce64b8f8b6d36fe362e9d29/QR_code.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actiontech/sqle-ui/e0259511cddb59fd3ce64b8f8b6d36fe362e9d29/public/favicon.png -------------------------------------------------------------------------------- /public/static/image/report_statistics_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actiontech/sqle-ui/e0259511cddb59fd3ce64b8f8b6d36fe362e9d29/public/static/image/report_statistics_preview.png -------------------------------------------------------------------------------- /public/static/image/white_list_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actiontech/sqle-ui/e0259511cddb59fd3ce64b8f8b6d36fe362e9d29/public/static/image/white_list_preview.png -------------------------------------------------------------------------------- /scripts/getGitVersion.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | let version = ''; 5 | let branch = child_process.execSync('git rev-parse --abbrev-ref HEAD', { 6 | encoding: 'utf8', 7 | }); 8 | let commitId = child_process.execSync('git rev-parse --short HEAD', { 9 | encoding: 'utf8', 10 | }); 11 | version = `${branch.split('\n')[0]} ${commitId.split('\n')[0]}`; 12 | const filePath = path.resolve(process.cwd(), './src/scripts/version.ts'); 13 | 14 | const command = `export const UI_VERSION="${version}"`; 15 | fs.writeFileSync(filePath, command); 16 | -------------------------------------------------------------------------------- /scripts/moveAntdCss.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | const cwd = process.cwd(); 5 | 6 | const antdStyle = path.resolve(cwd, './node_modules/antd/dist'); 7 | const lightCss = path.resolve(antdStyle, './antd.min.css'); 8 | const lightCssMap = path.resolve(antdStyle, './antd.min.css.map'); 9 | const darkCss = path.resolve(antdStyle, './antd.dark.min.css'); 10 | const darkCssMap = path.resolve(antdStyle, './antd.dark.min.css.map'); 11 | 12 | const publicPath = path.resolve(cwd, './public/static/css'); 13 | 14 | const pathSet = [lightCss, lightCssMap, darkCss, darkCssMap, publicPath]; 15 | 16 | for (const p of pathSet) { 17 | if (!fs.existsSync(p)) { 18 | console.error(`not find file by ${p}`); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | fs.copyFileSync(lightCss, `${publicPath}/antd.min.css`); 24 | fs.copyFileSync(lightCssMap, `${publicPath}/antd.min.css.map`); 25 | fs.copyFileSync(darkCss, `${publicPath}/antd.dark.min.css`); 26 | fs.copyFileSync(darkCssMap, `${publicPath}/antd.dark.min.css.map`); 27 | -------------------------------------------------------------------------------- /src/api/audit_whitelist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IGetAuditWhitelistResV1, 3 | ICreateAuditWhitelistReqV1, 4 | IBaseRes, 5 | IUpdateAuditWhitelistReqV1 6 | } from '../common.d'; 7 | 8 | export interface IGetAuditWhitelistV1Params { 9 | project_name: string; 10 | 11 | page_index: string; 12 | 13 | page_size: string; 14 | } 15 | 16 | export interface IGetAuditWhitelistV1Return extends IGetAuditWhitelistResV1 {} 17 | 18 | export interface ICreateAuditWhitelistV1Params 19 | extends ICreateAuditWhitelistReqV1 { 20 | project_name: string; 21 | } 22 | 23 | export interface ICreateAuditWhitelistV1Return extends IBaseRes {} 24 | 25 | export interface IDeleteAuditWhitelistByIdV1Params { 26 | project_name: string; 27 | 28 | audit_whitelist_id: string; 29 | } 30 | 31 | export interface IDeleteAuditWhitelistByIdV1Return extends IBaseRes {} 32 | 33 | export interface IUpdateAuditWhitelistByIdV1Params 34 | extends IUpdateAuditWhitelistReqV1 { 35 | project_name: string; 36 | 37 | audit_whitelist_id: string; 38 | } 39 | 40 | export interface IUpdateAuditWhitelistByIdV1Return extends IBaseRes {} 41 | -------------------------------------------------------------------------------- /src/api/companyNotice/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IGetCompanyNoticeResp, 3 | IUpdateCompanyNoticeReq, 4 | IBaseRes 5 | } from '../common.d'; 6 | 7 | export interface IGetCompanyNoticeReturn extends IGetCompanyNoticeResp {} 8 | 9 | export interface IUpdateCompanyNoticeParams extends IUpdateCompanyNoticeReq {} 10 | 11 | export interface IUpdateCompanyNoticeReturn extends IBaseRes {} 12 | -------------------------------------------------------------------------------- /src/api/companyNotice/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-identical-functions */ 2 | /* tslint:disable no-useless-cast */ 3 | /* tslint:disable no-unnecessary-type-assertion */ 4 | /* tslint:disable no-big-function */ 5 | /* tslint:disable no-duplicate-string */ 6 | import ServiceBase from '../Service.base'; 7 | import { AxiosRequestConfig } from 'axios'; 8 | 9 | import { 10 | IGetCompanyNoticeReturn, 11 | IUpdateCompanyNoticeParams, 12 | IUpdateCompanyNoticeReturn 13 | } from './index.d'; 14 | 15 | class CompanyNoticeService extends ServiceBase { 16 | public getCompanyNotice(options?: AxiosRequestConfig) { 17 | return this.get( 18 | '/v1/company_notice', 19 | undefined, 20 | options 21 | ); 22 | } 23 | 24 | public updateCompanyNotice( 25 | params: IUpdateCompanyNoticeParams, 26 | options?: AxiosRequestConfig 27 | ) { 28 | const paramsData = this.cloneDeep(params); 29 | return this.patch( 30 | '/v1/company_notice', 31 | paramsData, 32 | options 33 | ); 34 | } 35 | } 36 | 37 | export default new CompanyNoticeService(); 38 | -------------------------------------------------------------------------------- /src/api/dashboard/index.d.ts: -------------------------------------------------------------------------------- 1 | import { IGetDashboardResV1, IGetDashboardProjectTipsResV1 } from '../common.d'; 2 | 3 | export interface IGetDashboardV1Params { 4 | filter_project_name?: string; 5 | } 6 | 7 | export interface IGetDashboardV1Return extends IGetDashboardResV1 {} 8 | 9 | export interface IGetDashboardProjectTipsV1Return 10 | extends IGetDashboardProjectTipsResV1 {} 11 | -------------------------------------------------------------------------------- /src/api/dashboard/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-identical-functions */ 2 | /* tslint:disable no-useless-cast */ 3 | /* tslint:disable no-unnecessary-type-assertion */ 4 | /* tslint:disable no-big-function */ 5 | /* tslint:disable no-duplicate-string */ 6 | import ServiceBase from '../Service.base'; 7 | import { AxiosRequestConfig } from 'axios'; 8 | 9 | import { 10 | IGetDashboardV1Params, 11 | IGetDashboardV1Return, 12 | IGetDashboardProjectTipsV1Return 13 | } from './index.d'; 14 | 15 | class DashboardService extends ServiceBase { 16 | public getDashboardV1( 17 | params: IGetDashboardV1Params, 18 | options?: AxiosRequestConfig 19 | ) { 20 | const paramsData = this.cloneDeep(params); 21 | return this.get( 22 | '/v1/dashboard', 23 | paramsData, 24 | options 25 | ); 26 | } 27 | 28 | public getDashboardProjectTipsV1(options?: AxiosRequestConfig) { 29 | return this.get( 30 | '/v1/dashboard/project_tips', 31 | undefined, 32 | options 33 | ); 34 | } 35 | } 36 | 37 | export default new DashboardService(); 38 | -------------------------------------------------------------------------------- /src/api/global/index.d.ts: -------------------------------------------------------------------------------- 1 | import { IGetSQLEInfoResV1 } from '../common.d'; 2 | 3 | export interface IGetSQLEInfoV1Return extends IGetSQLEInfoResV1 {} 4 | -------------------------------------------------------------------------------- /src/api/global/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-identical-functions */ 2 | /* tslint:disable no-useless-cast */ 3 | /* tslint:disable no-unnecessary-type-assertion */ 4 | /* tslint:disable no-big-function */ 5 | /* tslint:disable no-duplicate-string */ 6 | import ServiceBase from '../Service.base'; 7 | import { AxiosRequestConfig } from 'axios'; 8 | 9 | import { IGetSQLEInfoV1Return } from './index.d'; 10 | 11 | class GlobalService extends ServiceBase { 12 | public getSQLEInfoV1(options?: AxiosRequestConfig) { 13 | return this.get('/v1/basic_info', undefined, options); 14 | } 15 | } 16 | 17 | export default new GlobalService(); 18 | -------------------------------------------------------------------------------- /src/api/instance/index.enum.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-duplicate-string */ 2 | 3 | export enum getInstanceTipListV1FunctionalModuleEnum { 4 | 'create_audit_plan' = 'create_audit_plan', 5 | 6 | 'create_workflow' = 'create_workflow', 7 | 8 | 'sql_manage' = 'sql_manage' 9 | } 10 | -------------------------------------------------------------------------------- /src/api/management_permission/index.d.ts: -------------------------------------------------------------------------------- 1 | import { IGetManagementPermissionsResV1 } from '../common.d'; 2 | 3 | export interface IGetManagementPermissionsV1Return 4 | extends IGetManagementPermissionsResV1 {} 5 | -------------------------------------------------------------------------------- /src/api/management_permission/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-identical-functions */ 2 | /* tslint:disable no-useless-cast */ 3 | /* tslint:disable no-unnecessary-type-assertion */ 4 | /* tslint:disable no-big-function */ 5 | /* tslint:disable no-duplicate-string */ 6 | import ServiceBase from '../Service.base'; 7 | import { AxiosRequestConfig } from 'axios'; 8 | 9 | import { IGetManagementPermissionsV1Return } from './index.d'; 10 | 11 | class ManagementPermissionService extends ServiceBase { 12 | public GetManagementPermissionsV1(options?: AxiosRequestConfig) { 13 | return this.get( 14 | '/v1/management_permissions', 15 | undefined, 16 | options 17 | ); 18 | } 19 | } 20 | 21 | export default new ManagementPermissionService(); 22 | -------------------------------------------------------------------------------- /src/api/oauth2/index.d.ts: -------------------------------------------------------------------------------- 1 | import { IBindOauth2UserReqV1, IBindOauth2UserResV1 } from '../common.d'; 2 | 3 | export interface IBindOauth2UserParams extends IBindOauth2UserReqV1 {} 4 | 5 | export interface IBindOauth2UserReturn extends IBindOauth2UserResV1 {} 6 | -------------------------------------------------------------------------------- /src/api/oauth2/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-identical-functions */ 2 | /* tslint:disable no-useless-cast */ 3 | /* tslint:disable no-unnecessary-type-assertion */ 4 | /* tslint:disable no-big-function */ 5 | /* tslint:disable no-duplicate-string */ 6 | import ServiceBase from '../Service.base'; 7 | import { AxiosRequestConfig } from 'axios'; 8 | 9 | import { IBindOauth2UserParams, IBindOauth2UserReturn } from './index.d'; 10 | 11 | class Oauth2Service extends ServiceBase { 12 | public Oauth2Link(options?: AxiosRequestConfig) { 13 | return this.get('/v1/oauth2/link', undefined, options); 14 | } 15 | 16 | public bindOauth2User( 17 | params: IBindOauth2UserParams, 18 | options?: AxiosRequestConfig 19 | ) { 20 | const paramsData = this.cloneDeep(params); 21 | return this.post( 22 | '/v1/oauth2/user/bind', 23 | paramsData, 24 | options 25 | ); 26 | } 27 | } 28 | 29 | export default new Oauth2Service(); 30 | -------------------------------------------------------------------------------- /src/api/operation/index.d.ts: -------------------------------------------------------------------------------- 1 | import { IGetOperationsResV1 } from '../common.d'; 2 | 3 | export interface IGetOperationsV1Return extends IGetOperationsResV1 {} 4 | -------------------------------------------------------------------------------- /src/api/operation/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-identical-functions */ 2 | /* tslint:disable no-useless-cast */ 3 | /* tslint:disable no-unnecessary-type-assertion */ 4 | /* tslint:disable no-big-function */ 5 | /* tslint:disable no-duplicate-string */ 6 | import ServiceBase from '../Service.base'; 7 | import { AxiosRequestConfig } from 'axios'; 8 | 9 | import { IGetOperationsV1Return } from './index.d'; 10 | 11 | class OperationService extends ServiceBase { 12 | public GetOperationsV1(options?: AxiosRequestConfig) { 13 | return this.get( 14 | '/v1/operations', 15 | undefined, 16 | options 17 | ); 18 | } 19 | } 20 | 21 | export default new OperationService(); 22 | -------------------------------------------------------------------------------- /src/api/project/index.enum.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-duplicate-string */ 2 | 3 | export enum getProjectTipsV1FunctionalModuleEnum { 4 | 'operation_record' = 'operation_record' 5 | } 6 | -------------------------------------------------------------------------------- /src/api/role/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IGetRoleTipsResV1, 3 | IGetRolesResV1, 4 | ICreateRoleReqV1, 5 | IBaseRes, 6 | IUpdateRoleReqV1 7 | } from '../common.d'; 8 | 9 | export interface IGetRoleTipListV1Return extends IGetRoleTipsResV1 {} 10 | 11 | export interface IGetRoleListV1Params { 12 | filter_role_name?: string; 13 | 14 | page_index: number; 15 | 16 | page_size: number; 17 | } 18 | 19 | export interface IGetRoleListV1Return extends IGetRolesResV1 {} 20 | 21 | export interface ICreateRoleV1Params extends ICreateRoleReqV1 {} 22 | 23 | export interface ICreateRoleV1Return extends IBaseRes {} 24 | 25 | export interface IDeleteRoleV1Params { 26 | role_name: string; 27 | } 28 | 29 | export interface IDeleteRoleV1Return extends IBaseRes {} 30 | 31 | export interface IUpdateRoleV1Params extends IUpdateRoleReqV1 { 32 | role_name: string; 33 | } 34 | 35 | export interface IUpdateRoleV1Return extends IBaseRes {} 36 | -------------------------------------------------------------------------------- /src/api/sql_analysis/index.d.ts: -------------------------------------------------------------------------------- 1 | import { IDirectGetSQLAnalysisResV1 } from '../common.d'; 2 | 3 | export interface IDirectGetSQLAnalysisV1Params { 4 | project_name: string; 5 | 6 | instance_name: string; 7 | 8 | schema_name?: string; 9 | 10 | sql?: string; 11 | } 12 | 13 | export interface IDirectGetSQLAnalysisV1Return 14 | extends IDirectGetSQLAnalysisResV1 {} 15 | -------------------------------------------------------------------------------- /src/api/sql_analysis/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-identical-functions */ 2 | /* tslint:disable no-useless-cast */ 3 | /* tslint:disable no-unnecessary-type-assertion */ 4 | /* tslint:disable no-big-function */ 5 | /* tslint:disable no-duplicate-string */ 6 | import ServiceBase from '../Service.base'; 7 | import { AxiosRequestConfig } from 'axios'; 8 | 9 | import { 10 | IDirectGetSQLAnalysisV1Params, 11 | IDirectGetSQLAnalysisV1Return 12 | } from './index.d'; 13 | 14 | class SqlAnalysisService extends ServiceBase { 15 | public directGetSQLAnalysisV1( 16 | params: IDirectGetSQLAnalysisV1Params, 17 | options?: AxiosRequestConfig 18 | ) { 19 | const paramsData = this.cloneDeep(params); 20 | return this.get( 21 | '/v1/sql_analysis', 22 | paramsData, 23 | options 24 | ); 25 | } 26 | } 27 | 28 | export default new SqlAnalysisService(); 29 | -------------------------------------------------------------------------------- /src/api/sql_audit/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IDirectAuditFileReqV1, 3 | IDirectAuditResV1, 4 | IDirectAuditReqV1, 5 | IDirectAuditFileReqV2, 6 | IDirectAuditResV2, 7 | IDirectAuditReqV2 8 | } from '../common.d'; 9 | 10 | export interface IDirectAuditFilesV1Params extends IDirectAuditFileReqV1 {} 11 | 12 | export interface IDirectAuditFilesV1Return extends IDirectAuditResV1 {} 13 | 14 | export interface IDirectAuditV1Params extends IDirectAuditReqV1 {} 15 | 16 | export interface IDirectAuditV1Return extends IDirectAuditResV1 {} 17 | 18 | export interface IDirectAuditFilesV2Params extends IDirectAuditFileReqV2 {} 19 | 20 | export interface IDirectAuditFilesV2Return extends IDirectAuditResV2 {} 21 | 22 | export interface IDirectAuditV2Params extends IDirectAuditReqV2 {} 23 | 24 | export interface IDirectAuditV2Return extends IDirectAuditResV2 {} 25 | -------------------------------------------------------------------------------- /src/api/sql_audit_record/index.enum.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-duplicate-string */ 2 | 3 | export enum getSQLAuditRecordsV1FilterSqlAuditStatusEnum { 4 | 'auditing' = 'auditing', 5 | 6 | 'successfully' = 'successfully' 7 | } 8 | -------------------------------------------------------------------------------- /src/api/sql_query/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IGetSqlExplainReqV1, 3 | IGetSQLExplainResV1, 4 | IGetSQLQueryHistoryResV1, 5 | IPrepareSQLQueryReqV1, 6 | IPrepareSQLQueryResV1, 7 | IGetSQLResultResV1 8 | } from '../common.d'; 9 | 10 | export interface IGetSQLExplainParams extends IGetSqlExplainReqV1 { 11 | instance_name: string; 12 | } 13 | 14 | export interface IGetSQLExplainReturn extends IGetSQLExplainResV1 {} 15 | 16 | export interface IGetSQLQueryHistoryParams { 17 | instance_name: string; 18 | 19 | filter_fuzzy_search?: string; 20 | 21 | page_index?: number; 22 | 23 | page_size?: number; 24 | } 25 | 26 | export interface IGetSQLQueryHistoryReturn extends IGetSQLQueryHistoryResV1 {} 27 | 28 | export interface IPrepareSQLQueryParams extends IPrepareSQLQueryReqV1 { 29 | instance_name: string; 30 | } 31 | 32 | export interface IPrepareSQLQueryReturn extends IPrepareSQLQueryResV1 {} 33 | 34 | export interface IGetSQLResultParams { 35 | query_id: string; 36 | 37 | page_index: number; 38 | 39 | page_size: number; 40 | } 41 | 42 | export interface IGetSQLResultReturn extends IGetSQLResultResV1 {} 43 | -------------------------------------------------------------------------------- /src/api/workflow/index.enum.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-duplicate-string */ 2 | 3 | export enum getWorkflowsV1FilterStatusEnum { 4 | 'wait_for_audit' = 'wait_for_audit', 5 | 6 | 'wait_for_execution' = 'wait_for_execution', 7 | 8 | 'rejected' = 'rejected', 9 | 10 | 'executing' = 'executing', 11 | 12 | 'canceled' = 'canceled', 13 | 14 | 'exec_failed' = 'exec_failed', 15 | 16 | 'finished' = 'finished' 17 | } 18 | 19 | export enum exportWorkflowV1FilterStatusEnum { 20 | 'wait_for_audit' = 'wait_for_audit', 21 | 22 | 'wait_for_execution' = 'wait_for_execution', 23 | 24 | 'rejected' = 'rejected', 25 | 26 | 'executing' = 'executing', 27 | 28 | 'canceled' = 'canceled', 29 | 30 | 'exec_failed' = 'exec_failed', 31 | 32 | 'finished' = 'finished' 33 | } 34 | 35 | export enum getGlobalWorkflowsV1FilterStatusEnum { 36 | 'wait_for_audit' = 'wait_for_audit', 37 | 38 | 'wait_for_execution' = 'wait_for_execution', 39 | 40 | 'rejected' = 'rejected', 41 | 42 | 'executing' = 'executing', 43 | 44 | 'canceled' = 'canceled', 45 | 46 | 'exec_failed' = 'exec_failed', 47 | 48 | 'finished' = 'finished' 49 | } 50 | -------------------------------------------------------------------------------- /src/assets/font/SourceHanSansSC-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actiontech/sqle-ui/e0259511cddb59fd3ce64b8f8b6d36fe362e9d29/src/assets/font/SourceHanSansSC-Bold.woff2 -------------------------------------------------------------------------------- /src/assets/img/login-left-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actiontech/sqle-ui/e0259511cddb59fd3ce64b8f8b6d36fe362e9d29/src/assets/img/login-left-bg.png -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actiontech/sqle-ui/e0259511cddb59fd3ce64b8f8b6d36fe362e9d29/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/moon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/zh-cn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/AuditResultErrorMessage/index.less: -------------------------------------------------------------------------------- 1 | .audit-result-error-message { 2 | max-width: 400px; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/AuditResultErrorMessage/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IAuditResult } from '../../api/common'; 2 | 3 | export type AuditResultErrorMessageProps = { 4 | auditResult?: IAuditResult[]; 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/BackButton/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BackButton should render a button 1`] = ` 4 |
5 | 13 |
14 | `; 15 | -------------------------------------------------------------------------------- /src/components/BackButton/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { fireEvent, render, screen } from '@testing-library/react'; 2 | import BackButton from '.'; 3 | import useBack from '../../hooks/useBack'; 4 | 5 | jest.mock('../../hooks/useBack', () => { 6 | const goBack = jest.fn(); 7 | return () => ({ 8 | goBack, 9 | }); 10 | }); 11 | 12 | describe('BackButton', () => { 13 | const goBack = useBack().goBack as jest.Mock; 14 | 15 | afterAll(() => { 16 | jest.resetAllMocks(); 17 | }); 18 | 19 | test('should render a button', () => { 20 | const { container } = render(); 21 | expect(container).toMatchSnapshot(); 22 | }); 23 | 24 | test('should call goBack function when click the button', () => { 25 | render(); 26 | fireEvent.click(screen.getByText('common.back')); 27 | 28 | expect(goBack).toBeCalledTimes(1); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/BackButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, ButtonProps } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import useBack from '../../hooks/useBack'; 4 | 5 | const BackButton: React.FC = (props) => { 6 | const { t } = useTranslation(); 7 | 8 | const { goBack } = useBack(); 9 | 10 | return ( 11 | 14 | ); 15 | }; 16 | 17 | export default BackButton; 18 | -------------------------------------------------------------------------------- /src/components/BackendForm/__snapshots__/BackendForm.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BackendForm should render form item by params key 1`] = ` 4 | 5 | 16 | 17 | 18 | 36 | 37 | 38 | 48 | 49 | 50 | 51 | `; 52 | -------------------------------------------------------------------------------- /src/components/BackendForm/index.ts: -------------------------------------------------------------------------------- 1 | export type FormItem = { 2 | desc?: string; 3 | key?: string; 4 | type?: string; 5 | value?: string; 6 | }; 7 | 8 | export type BackendFormRequestParams = { 9 | key?: string; 10 | value?: string; 11 | }; 12 | 13 | export type BackendFormValues = { 14 | [key: string]: string | boolean; 15 | }; 16 | 17 | export type BackendFormProps = { 18 | params: FormItem[]; 19 | paramsKey?: string; 20 | disabled?: boolean; 21 | }; 22 | 23 | export { default } from './BackendForm'; 24 | -------------------------------------------------------------------------------- /src/components/CopyIcon/__snapshots__/CopyIcon.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test CopyIcon should match snapshot 1`] = ` 4 | 5 |
6 |
9 | 14 | 27 | 28 |
29 |
30 | 31 | `; 32 | -------------------------------------------------------------------------------- /src/components/CopyIcon/index.less: -------------------------------------------------------------------------------- 1 | @import '../../styles/_variable.less'; 2 | 3 | .actiontech-copy { 4 | outline: none; 5 | cursor: pointer; 6 | transition: color 0.3s; 7 | margin-left: 4px; 8 | color: @bg-blue; 9 | } 10 | 11 | .actiontech-copy-success { 12 | color: @text-green; 13 | } 14 | -------------------------------------------------------------------------------- /src/components/CopyIcon/index.tsx: -------------------------------------------------------------------------------- 1 | export interface CopyIconProps { 2 | text?: string; 3 | onCopy?: (event?: React.MouseEvent) => void; 4 | tooltips?: boolean | React.ReactNode; 5 | format?: 'text/plain' | 'text/html'; 6 | children?: React.ReactNode; 7 | } 8 | 9 | export { default } from './CopyIcon'; 10 | -------------------------------------------------------------------------------- /src/components/CronInput/index.data.ts: -------------------------------------------------------------------------------- 1 | import { I18nKey } from '../../types/common.type'; 2 | 3 | export const weekLabel: { [key: number]: I18nKey } = { 4 | 0: 'common.week.sunday', 5 | 1: 'common.week.monday', 6 | 2: 'common.week.tuesday', 7 | 3: 'common.week.wednesday', 8 | 4: 'common.week.thursday', 9 | 5: 'common.week.friday', 10 | 6: 'common.week.saturday', 11 | }; 12 | -------------------------------------------------------------------------------- /src/components/CronInput/index.less: -------------------------------------------------------------------------------- 1 | .cron-input-wrapper { 2 | .cron-inline-select { 3 | min-width: 80px; 4 | } 5 | .cron-every-select { 6 | width: 100px; 7 | } 8 | } 9 | 10 | .cron-week-wrapper { 11 | padding-left: 40px; 12 | } 13 | 14 | .cron-inline-select-dropdown { 15 | max-width: 300px; 16 | .rc-virtual-list-holder-inner { 17 | flex-direction: row !important; 18 | flex-wrap: wrap; 19 | } 20 | .cron-inline-select-option { 21 | flex: 0 0 30px; 22 | } 23 | .anticon-check { 24 | display: none; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/CronInput/index.type.ts: -------------------------------------------------------------------------------- 1 | export enum CronMode { 2 | Manual = 'Manual', 3 | Select = 'Select', 4 | } 5 | 6 | export enum CronTimeValue { 7 | 'everyDay' = 'everyDay', 8 | 'everyWeek' = 'everyWeek', 9 | } 10 | 11 | export type CronInputProps = { 12 | everyDefault?: CronTimeValue; 13 | value?: string; 14 | onChange?: (value: string) => void; 15 | mode?: CronMode; 16 | modeChange?: (mode: CronMode) => void; 17 | updateErrorMessage?: (errorMessage: string) => void; 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/DatabaseTypeLogo/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test DatabaseTypeLogo should match snapshot 1`] = ` 4 |
5 |
8 | 12 | 15 | mysql 16 | 17 |
18 |
19 | `; 20 | 21 | exports[`test DatabaseTypeLogo should match snapshot 2`] = ` 22 |
23 |
26 | 30 | 33 | db2 34 | 35 |
36 |
37 | `; 38 | -------------------------------------------------------------------------------- /src/components/DatabaseTypeLogo/index.less: -------------------------------------------------------------------------------- 1 | .database-type-logo-wrapper { 2 | white-space: nowrap; 3 | overflow: hidden; 4 | display: flex; 5 | align-items: center; 6 | 7 | img { 8 | width: 16px; 9 | height: auto; 10 | background-color: #fff; 11 | } 12 | 13 | span { 14 | margin-left: 4px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/DatabaseTypeLogo/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import DatabaseTypeLogo from '.'; 3 | 4 | describe('test DatabaseTypeLogo', () => { 5 | test('should match snapshot', () => { 6 | const { container, rerender } = render(); 7 | 8 | expect(container).toMatchSnapshot(); 9 | 10 | rerender( 11 | 12 | ); 13 | expect(container).toMatchSnapshot(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/DatabaseTypeLogo/index.tsx: -------------------------------------------------------------------------------- 1 | import { SQLE_BASE_URL } from '../../data/common'; 2 | import './index.less'; 3 | const DatabaseTypeLogo: React.FC<{ dbType: string; logoUrl?: string }> = ({ 4 | dbType, 5 | logoUrl, 6 | }) => { 7 | return ( 8 |
9 | 16 | {dbType} 17 |
18 | ); 19 | }; 20 | export default DatabaseTypeLogo; 21 | -------------------------------------------------------------------------------- /src/components/EditText/EditText.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from 'antd'; 2 | import { useRef } from 'react'; 3 | import { EditTextProps } from '.'; 4 | 5 | const EditText: React.FC = (props) => { 6 | const { editable, ...otherProps } = props; 7 | 8 | const val = useRef( 9 | typeof props.children === 'string' ? props.children : '' 10 | ); 11 | 12 | return ( 13 | { 19 | val.current = value; 20 | editable?.onChange?.(value); 21 | }, 22 | onEnd: () => { 23 | editable?.onEnd?.(val.current); 24 | }, 25 | }} 26 | /> 27 | ); 28 | }; 29 | 30 | export default EditText; 31 | -------------------------------------------------------------------------------- /src/components/EditText/index.tsx: -------------------------------------------------------------------------------- 1 | import { ParagraphProps } from 'antd/lib/typography/Paragraph'; 2 | 3 | type EditTextEditableProps = Omit< 4 | Exclude['editable'], boolean>, 5 | 'onEnd' 6 | > & { 7 | onEnd?: (value: string) => void; 8 | }; 9 | 10 | export interface EditTextProps extends Omit { 11 | editable?: EditTextEditableProps; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/EmptyBox/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`EmptyBox should render children by props of "if" 1`] = `
`; 4 | 5 | exports[`EmptyBox should render children by props of "if" 2`] = ` 6 |
7 | visible 8 |
9 | `; 10 | 11 | exports[`EmptyBox should render default node when "if" is falsy and "defaultNode" is truthy 1`] = ` 12 |
13 | show default node 14 |
15 | `; 16 | 17 | exports[`EmptyBox should render default node when "if" is falsy and "defaultNode" is truthy 2`] = ` 18 |
19 | visible 20 |
21 | `; 22 | -------------------------------------------------------------------------------- /src/components/EmptyBox/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import EmptyBox from '.'; 3 | 4 | describe('EmptyBox', () => { 5 | test('should render children by props of "if"', () => { 6 | const { container, rerender } = render(not visible); 7 | expect(container).toMatchSnapshot(); 8 | rerender(visible); 9 | expect(container).toMatchSnapshot(); 10 | }); 11 | test('should render default node when "if" is falsy and "defaultNode" is truthy', () => { 12 | const { container, rerender } = render( 13 | 14 | not visible 15 | 16 | ); 17 | expect(container).toMatchSnapshot(); 18 | rerender( 19 | 20 | visible 21 | 22 | ); 23 | expect(container).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/components/EmptyBox/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const EmptyBox: React.FC<{ 4 | if?: boolean; 5 | defaultNode?: React.ReactElement | JSX.Element | string; 6 | children?: React.ReactNode; 7 | }> = (props) => { 8 | if (!props.if) { 9 | return <>{props.defaultNode ?? null}; 10 | } 11 | return <>{props.children}; 12 | }; 13 | 14 | export default EmptyBox; 15 | -------------------------------------------------------------------------------- /src/components/EnterpriseFeatureDisplay/EnterpriseFeatureDisplay.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import EnterpriseFeatureDisplay from './EnterpriseFeatureDisplay'; 3 | import { Space } from 'antd'; 4 | 5 | const children = <>ee version display; 6 | 7 | describe('test EnterpriseFeatureDisplay', () => { 8 | test('should match snapshot', () => { 9 | const { container, rerender } = render( 10 | 14 | {children} 15 | 16 | ); 17 | 18 | expect(container).toMatchSnapshot(); 19 | 20 | rerender( 21 | 26 | test\ntest\n 27 | test\n 28 | 29 | } 30 | > 31 | {children} 32 | 33 | ); 34 | 35 | expect(container).toMatchSnapshot(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/components/EnterpriseFeatureDisplay/index.tsx: -------------------------------------------------------------------------------- 1 | export type EnterpriseFeatureDisplayProps = { 2 | children: React.ReactNode; 3 | eeFeatureDescription: React.ReactNode; 4 | featureName: string; 5 | clearCEWrapperPadding?: boolean; 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/FooterButtonWrapper/FooterButtonWrapper.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import FooterButtonWrapper from '.'; 3 | 4 | const children1 = ; 5 | const children2 = ; 6 | 7 | describe('test FooterButtonWrapper', () => { 8 | it('should match snapshot', () => { 9 | const { container, rerender } = render( 10 | 11 | {children1} 12 | {children2} 13 | 14 | ); 15 | 16 | expect(container).toMatchSnapshot(); 17 | 18 | rerender( 19 | 20 | {children1} 21 | {children2} 22 | 23 | ); 24 | expect(container).toMatchSnapshot(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/components/FooterButtonWrapper/FooterButtonWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Card, Space } from 'antd'; 2 | import React from 'react'; 3 | import './index.less'; 4 | import { FooterButtonWrapperProps } from '.'; 5 | 6 | const FooterButtonWrapper: React.FC = ({ 7 | children, 8 | insideProject = true, 9 | }) => { 10 | return ( 11 |
16 | 23 | 24 | {children} 25 | 26 | 27 |
28 | ); 29 | }; 30 | 31 | export default FooterButtonWrapper; 32 | -------------------------------------------------------------------------------- /src/components/FooterButtonWrapper/index.less: -------------------------------------------------------------------------------- 1 | .footer-button-wrapper { 2 | position: fixed; 3 | right: 0; 4 | bottom: 0; 5 | z-index: 99; 6 | box-shadow: 0 -6px 16px rgb(0 0 0 / 8%); 7 | width: 100%; 8 | } 9 | 10 | .inside-the-project-footer-button-wrapper { 11 | width: calc(100% - 200px); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/FooterButtonWrapper/index.tsx: -------------------------------------------------------------------------------- 1 | import FooterButtonWrapper from './FooterButtonWrapper'; 2 | 3 | export type FooterButtonWrapperProps = { 4 | children: React.ReactNode; 5 | insideProject?: boolean; 6 | }; 7 | 8 | export default FooterButtonWrapper; 9 | -------------------------------------------------------------------------------- /src/components/HeaderProgress/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import HeaderProgress from '.'; 3 | import MockNprogress from 'nprogress'; 4 | 5 | jest.mock('nprogress', () => ({ 6 | configure: jest.fn(), 7 | start: jest.fn(), 8 | done: jest.fn(), 9 | })); 10 | 11 | describe('HeaderProgress', () => { 12 | afterAll(() => { 13 | jest.resetAllMocks(); 14 | }); 15 | 16 | test('should render children by props of "if"', () => { 17 | const { unmount } = render(); 18 | expect(MockNprogress.start).toBeCalledTimes(1); 19 | unmount(); 20 | expect(MockNprogress.done).toBeCalledTimes(1); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/components/HeaderProgress/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Nprogress from 'nprogress'; 3 | Nprogress.configure({ 4 | showSpinner: false, 5 | }); 6 | 7 | const HeaderProgress = () => { 8 | React.useEffect(() => { 9 | Nprogress.start(); 10 | return () => { 11 | Nprogress.done(); 12 | }; 13 | }, []); 14 | 15 | return null; 16 | }; 17 | 18 | export default HeaderProgress; 19 | -------------------------------------------------------------------------------- /src/components/IconTipsLabel/IconTipsLabel.test.tsx: -------------------------------------------------------------------------------- 1 | import { fireEvent, render, screen, waitFor } from '@testing-library/react'; 2 | import { getBySelector } from '../../testUtils/customQuery'; 3 | import IconTipsLabel from './IconTipsLabel'; 4 | 5 | describe('IconTipsLabel', () => { 6 | it('should match snapshot', async () => { 7 | jest.useFakeTimers(); 8 | const { baseElement, rerender } = render( 9 | 10 |

123

11 |
12 | ); 13 | 14 | expect(baseElement).toMatchSnapshot(); 15 | 16 | fireEvent.mouseEnter(getBySelector('.text-orange')); 17 | 18 | await waitFor(() => { 19 | screen.getByText('this is tips'); 20 | }); 21 | 22 | expect(baseElement).toMatchSnapshot(); 23 | 24 | rerender( 25 | 29 |

123

30 |
31 | ); 32 | expect(baseElement).toMatchSnapshot(); 33 | 34 | jest.useRealTimers(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/components/IconTipsLabel/IconTipsLabel.tsx: -------------------------------------------------------------------------------- 1 | import { InfoCircleOutlined } from '@ant-design/icons'; 2 | import { Space, Tooltip } from 'antd'; 3 | import { IIconTipsLabelProps } from '.'; 4 | 5 | const IconTipsLabel: React.FC = (props) => { 6 | const { tips, children, iconStyle } = props; 7 | 8 | return ( 9 | 10 | 11 | 12 | 13 | {children} 14 | 15 | ); 16 | }; 17 | 18 | export default IconTipsLabel; 19 | -------------------------------------------------------------------------------- /src/components/IconTipsLabel/index.ts: -------------------------------------------------------------------------------- 1 | import IconTipsLabel from './IconTipsLabel'; 2 | 3 | export type IIconTipsLabelProps = { 4 | tips?: string | React.ReactNode | JSX.Element; 5 | iconStyle?: React.CSSProperties; 6 | children?: React.ReactNode; 7 | }; 8 | 9 | export default IconTipsLabel; 10 | -------------------------------------------------------------------------------- /src/components/Link/Link.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import { CustomLinkProps } from '.'; 3 | import { SQLE_BASE_URL } from '../../data/common'; 4 | 5 | const CustomLink: React.FC = ({ to, ...props }) => { 6 | let path = to; 7 | 8 | if (typeof to === 'string') { 9 | path = to.startsWith(SQLE_BASE_URL) ? to : `${SQLE_BASE_URL}${to}`; 10 | } else { 11 | path = { 12 | ...to, 13 | pathname: to.pathname 14 | ? to.pathname.startsWith(SQLE_BASE_URL) 15 | ? to.pathname 16 | : `${SQLE_BASE_URL}${to.pathname}` 17 | : undefined, 18 | }; 19 | } 20 | return ; 21 | }; 22 | 23 | export default CustomLink; 24 | -------------------------------------------------------------------------------- /src/components/Link/index.tsx: -------------------------------------------------------------------------------- 1 | import { LinkProps } from 'react-router-dom'; 2 | export { default as Link } from './Link'; 3 | 4 | export type CustomLinkProps = LinkProps & 5 | React.RefAttributes; 6 | -------------------------------------------------------------------------------- /src/components/Nav/Header/Component/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux'; 2 | import logo from '../../../../assets/img/logo.png'; 3 | import { IReduxState } from '../../../../store'; 4 | import { Link } from '../../../Link'; 5 | 6 | const Logo: React.FC = () => { 7 | const { webLogoUrl, webTitle } = useSelector((state: IReduxState) => ({ 8 | webTitle: state.system.webTitle, 9 | webLogoUrl: state.system.webLogoUrl, 10 | })); 11 | return ( 12 | 13 |
14 | 15 | {webTitle} 16 |
17 | 18 | ); 19 | }; 20 | 21 | export default Logo; 22 | -------------------------------------------------------------------------------- /src/components/Nav/Header/Component/OperationRecordNavigation.tsx: -------------------------------------------------------------------------------- 1 | import { HistoryOutlined } from '@ant-design/icons'; 2 | import useCurrentUser from '../../../../hooks/useCurrentUser'; 3 | import useNavigate from '../../../../hooks/useNavigate'; 4 | import EmptyBox from '../../../EmptyBox'; 5 | 6 | const OperationRecordNavigation: React.FC = () => { 7 | const navigate = useNavigate(); 8 | const { isAdmin } = useCurrentUser(); 9 | 10 | return ( 11 | 12 | { 14 | navigate('operationRecord'); 15 | }} 16 | className="header-operation-record-icon" 17 | /> 18 | 19 | ); 20 | }; 21 | 22 | export default OperationRecordNavigation; 23 | -------------------------------------------------------------------------------- /src/components/Nav/Header/Component/__test__/Logo.test.tsx: -------------------------------------------------------------------------------- 1 | import { SQLE_DEFAULT_WEB_TITLE } from '../../../../../data/common'; 2 | import { renderWithRouterAndRedux } from '../../../../../testUtils/customRender'; 3 | import Logo from '../Logo'; 4 | 5 | describe('test Nav/Header/Logo', () => { 6 | test('should match snapshot', () => { 7 | const { container } = renderWithRouterAndRedux(, undefined, { 8 | system: { webTitle: SQLE_DEFAULT_WEB_TITLE, webLogoUrl: 'test' }, 9 | }); 10 | expect(container).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/components/Nav/Header/Component/__test__/__snapshots__/Logo.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test Nav/Header/Logo should match snapshot 1`] = ` 4 | 19 | `; 20 | -------------------------------------------------------------------------------- /src/components/Nav/Header/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import { ModalName } from '../../../../data/ModalName'; 4 | import { initNavModalStatus } from '../../../../store/nav'; 5 | import VersionModal from './VersionModal'; 6 | 7 | /* IFTRUE_isEE */ 8 | import CompanyNoticeModal from './CompanyNoticeModal'; 9 | /* FITRUE_isEE */ 10 | 11 | const InfoModalManager = () => { 12 | const dispatch = useDispatch(); 13 | 14 | useEffect(() => { 15 | dispatch( 16 | initNavModalStatus({ 17 | modalStatus: { 18 | [ModalName.SHOW_VERSION]: false, 19 | 20 | /* IFTRUE_isEE */ 21 | [ModalName.Company_Notice]: false, 22 | /* FITRUE_isEE */ 23 | }, 24 | }) 25 | ); 26 | }, [dispatch]); 27 | 28 | return ( 29 | <> 30 | 31 | 32 | {/* IFTRUE_isEE */} 33 | 34 | {/* FITRUE_isEE */} 35 | 36 | ); 37 | }; 38 | 39 | export default InfoModalManager; 40 | -------------------------------------------------------------------------------- /src/components/Nav/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import { Space } from 'antd'; 2 | import HeaderMenu from './Component/HeaderMenu'; 3 | import Logo from './Component/Logo'; 4 | import MoreAction from './Component/MoreAction'; 5 | import UserNavigation from './Component/UserNavigation'; 6 | import NavModal from './Modal'; 7 | 8 | /* IFTRUE_isEE */ 9 | import OperationRecordNavigation from './Component/OperationRecordNavigation'; 10 | import CompanyNoticeTrigger from './Component/CompanyNoticeTrigger'; 11 | /* FITRUE_isEE */ 12 | 13 | const Header: React.FC = () => { 14 | return ( 15 | <> 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | {/* IFTRUE_isEE */} 24 | 25 | 26 | {/* FITRUE_isEE */} 27 | 28 | {/* */} 29 | 30 | 31 | 32 |
33 | 34 | 35 | ); 36 | }; 37 | 38 | export default Header; 39 | -------------------------------------------------------------------------------- /src/components/Nav/index.tsx: -------------------------------------------------------------------------------- 1 | import { Layout } from 'antd'; 2 | import React from 'react'; 3 | import useStyles from '../../theme'; 4 | import Header from './Header'; 5 | import EmptyBox from '../EmptyBox'; 6 | import { useSelector } from 'react-redux'; 7 | import { IReduxState } from '../../store'; 8 | 9 | import './index.less'; 10 | 11 | const Nav: React.FC<{ children: React.ReactNode }> = (props) => { 12 | const username = useSelector( 13 | (state) => state.user.username 14 | ); 15 | const styles = useStyles(); 16 | 17 | return ( 18 | 19 | 20 | 21 |
22 | 23 | 24 | {props.children} 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default Nav; 32 | -------------------------------------------------------------------------------- /src/components/OrderStatusTag/index.type.ts: -------------------------------------------------------------------------------- 1 | import { TagProps } from 'antd'; 2 | import { WorkflowRecordResV2StatusEnum } from '../../api/common.enum'; 3 | import { I18nKey } from '../../types/common.type'; 4 | 5 | export type OrderStatus = { 6 | [key in WorkflowRecordResV2StatusEnum | 'unknown']: { 7 | color: TagProps['color']; 8 | label: I18nKey; 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/RuleList/RuleLevelIcon/index.less: -------------------------------------------------------------------------------- 1 | .sqle-rule-icon { 2 | width: 50px; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/RuleList/index.type.ts: -------------------------------------------------------------------------------- 1 | import { ListProps } from 'antd'; 2 | import React from 'react'; 3 | import { IRuleResV1 } from '../../api/common.d'; 4 | 5 | export type RuleListProps = { 6 | list: IRuleResV1[]; 7 | actions?: (item: IRuleResV1) => React.ReactNode[] | undefined; 8 | listProps?: ListProps; 9 | tabChange?: (tab: string) => void; 10 | currentTab?: string; 11 | allRuleTabs?: string[]; 12 | }; 13 | 14 | export type TabRuleItem = { 15 | tabTitle: string; 16 | rules: IRuleResV1[]; 17 | len: number; 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/RuleList/useSyncRuleListTab/index.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react'; 2 | import { IRuleResV1 } from '../../../api/common'; 3 | import { RuleListDefaultTabKey } from '../../../data/common'; 4 | 5 | const useSyncRuleListTab = (allRules?: IRuleResV1[]) => { 6 | const [tabKey, setTabKey] = useState(RuleListDefaultTabKey); 7 | const [allTypes, setAllTypes] = useState([]); 8 | 9 | const tabChange = useCallback((currentKey: string) => { 10 | setTabKey(currentKey); 11 | }, []); 12 | 13 | useEffect(() => { 14 | if (allRules && allRules.length > 0) { 15 | const set = new Set(); 16 | allRules.forEach((rule) => { 17 | if (rule.type) { 18 | set.add(rule.type); 19 | } 20 | }); 21 | setAllTypes(Array.from(set)); 22 | } 23 | }, [allRules]); 24 | 25 | return { 26 | tabKey, 27 | allTypes, 28 | setTabKey, 29 | tabChange, 30 | }; 31 | }; 32 | 33 | export default useSyncRuleListTab; 34 | -------------------------------------------------------------------------------- /src/components/TestDatabaseConnectButton/index.type.ts: -------------------------------------------------------------------------------- 1 | export type TestDatabaseConnectButtonProps = { 2 | initHide?: boolean; 3 | onClickTestButton: () => void; 4 | loading: boolean; 5 | connectAble: boolean; 6 | connectDisableReason?: string; 7 | }; 8 | -------------------------------------------------------------------------------- /src/data/StorageKey.ts: -------------------------------------------------------------------------------- 1 | enum StorageKey { 2 | Language = 'SQLE_LANGUAGE', 3 | Theme = 'SQLE_THEME', 4 | Token = 'SQLE_TOKEN', 5 | Username = 'SQLE_USERNAME', 6 | Project_Catch = 'SQLE_PROJECT_CATCH', 7 | SHOW_COMPANY_NOTICE = 'SHOW_COMPANY_NOTICE', 8 | } 9 | 10 | export default StorageKey; 11 | -------------------------------------------------------------------------------- /src/hooks/useBack/index.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import useBack from '.'; 3 | import useNavigate from '../useNavigate'; 4 | 5 | jest.mock('../useNavigate', () => jest.fn()); 6 | 7 | describe('useBack', () => { 8 | test('should jump to last path in history when call goBack', () => { 9 | const navigateSpy = jest.fn(); 10 | (useNavigate as jest.Mock).mockImplementation(() => navigateSpy); 11 | const { result } = renderHook(() => useBack()); 12 | result.current.goBack(); 13 | expect(navigateSpy).toBeCalledTimes(1); 14 | expect(navigateSpy).toBeCalledWith(-1); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/hooks/useBack/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import useNavigate from '../useNavigate'; 3 | 4 | const useBack = () => { 5 | const navigate = useNavigate(); 6 | 7 | const goBack = React.useCallback(() => { 8 | navigate(-1); 9 | }, [navigate]); 10 | 11 | return { goBack }; 12 | }; 13 | 14 | export default useBack; 15 | -------------------------------------------------------------------------------- /src/hooks/useBackendTable/index.ts: -------------------------------------------------------------------------------- 1 | import useBackendTable from './useBackendTable'; 2 | 3 | export default useBackendTable; 4 | -------------------------------------------------------------------------------- /src/hooks/useCron/index.type.ts: -------------------------------------------------------------------------------- 1 | export type CronOptions = { 2 | defaultValue?: string; 3 | defaultMonth?: string; 4 | defaultDay?: string; 5 | defaultWeek?: string; 6 | defaultHour?: string; 7 | defaultMinute?: string; 8 | }; 9 | -------------------------------------------------------------------------------- /src/hooks/useCurrentUser/index.test.data.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IManagementPermissionResV1, 3 | IUserBindProjectResV1, 4 | } from '../../api/common'; 5 | 6 | export const mockBindProjects: IUserBindProjectResV1[] = [ 7 | { 8 | is_manager: true, 9 | project_name: 'default', 10 | }, 11 | { 12 | is_manager: false, 13 | project_name: 'test', 14 | }, 15 | ]; 16 | 17 | export const mockManagementPermissions: IManagementPermissionResV1[] = [ 18 | { 19 | code: 1, 20 | desc: '创建项目', 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /src/hooks/useCurrentUser/index.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux'; 2 | import { IReduxState } from '../../store'; 3 | import { SystemRole } from '../../data/common'; 4 | import { useCallback } from 'react'; 5 | const useCurrentUser = () => { 6 | const { role, bindProjects, managementPermissions, username } = useSelector( 7 | (state: IReduxState) => { 8 | return { 9 | username: state.user.username, 10 | role: state.user.role, 11 | bindProjects: state.user.bindProjects, 12 | managementPermissions: state.user.managementPermissions, 13 | }; 14 | } 15 | ); 16 | const isAdmin: boolean = role === SystemRole.admin; 17 | 18 | const isProjectManager = useCallback( 19 | (projectName: string) => { 20 | const project = bindProjects.find((v) => v.project_name === projectName); 21 | return !!project && !!project.is_manager; 22 | }, 23 | [bindProjects] 24 | ); 25 | 26 | return { 27 | isAdmin, 28 | isProjectManager, 29 | bindProjects, 30 | managementPermissions, 31 | username, 32 | }; 33 | }; 34 | export default useCurrentUser; 35 | -------------------------------------------------------------------------------- /src/hooks/useDatabaseType/index.test.data.ts: -------------------------------------------------------------------------------- 1 | import { IDriverMeta } from '../../api/common'; 2 | 3 | /* 4 | * 禁止修改该数据现有的顺序, 否则会造成单元测试中模拟 选择数据源类型下拉数据出现异常. 5 | * 可以在数组尾部新增数据 6 | */ 7 | export const driverMeta: IDriverMeta[] = [ 8 | { 9 | default_port: 4443, 10 | driver_name: 'oracle', 11 | }, 12 | { 13 | default_port: 3306, 14 | driver_name: 'mysql', 15 | }, 16 | ]; 17 | -------------------------------------------------------------------------------- /src/hooks/useManagerPermission/index.ts: -------------------------------------------------------------------------------- 1 | import useManagerPermission from './useManagerPermission'; 2 | 3 | export default useManagerPermission; 4 | -------------------------------------------------------------------------------- /src/hooks/useMonacoEditor/index.type.ts: -------------------------------------------------------------------------------- 1 | export interface IRange { 2 | startLineNumber: number; 3 | endLineNumber: number; 4 | startColumn: number; 5 | endColumn: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/hooks/useMonacoEditor/regexLanguage.ts: -------------------------------------------------------------------------------- 1 | import { monaco } from 'react-monaco-editor'; 2 | 3 | export const regexMonarch: monaco.languages.IMonarchLanguage = { 4 | ignoreCase: false, 5 | 6 | tokenizer: { 7 | root: [ 8 | [ 9 | /\/(?=(?:\\.|[^\\/\\\n\r])+?\/[gimuy]{0,5}(?=\W|$))(?:\\.|[^\\/\\\n\r])+?\/[gimuy]{0,5}/, 10 | 'regexp', 11 | ], // match regular expression literals 12 | [/\$[\w$]+/, 'variable'], // match variables 13 | [/\b[A-Z]\w*\b/, 'type'], // match types 14 | [/\b(?:\d+\.?\d*|\.\d+)\b/, 'number'], // match numbers 15 | [/[{}[\]()?,.:;]/, 'delimiter'], // match delimiters 16 | ], 17 | }, 18 | defaultToken: 'invalid', 19 | }; 20 | 21 | export const regexLanguage: monaco.languages.ILanguageExtensionPoint = { 22 | id: 'regexp', 23 | aliases: ['Regexp', 'regexp'], 24 | mimetypes: ['text/regexp'], 25 | }; 26 | -------------------------------------------------------------------------------- /src/hooks/useNavigate/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test useNavigate correctly encodes the param in the URL and decodes the param when it is used 1`] = ` 4 |
5 |

6 | Blog: 7 | react router 8 |

9 |
10 | `; 11 | -------------------------------------------------------------------------------- /src/hooks/useOperation/index.tsx: -------------------------------------------------------------------------------- 1 | import useOperation from './useOperation'; 2 | 3 | export default useOperation; 4 | -------------------------------------------------------------------------------- /src/hooks/useStaticStatus/index.type.ts: -------------------------------------------------------------------------------- 1 | import { I18nKey } from '../../types/common.type'; 2 | 3 | export type StaticEnumDictionary = { 4 | [key in T]: I18nKey; 5 | }; 6 | -------------------------------------------------------------------------------- /src/hooks/useTable/index.type.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary } from '../../types/common.type'; 2 | 3 | export type UseTableOption = { 4 | defaultPageIndex?: number; 5 | defaultPageSize?: number; 6 | defaultFilterInfo?: Dictionary; 7 | defaultFilterFormCollapse?: boolean; 8 | }; 9 | 10 | export type TablePagination = { 11 | pageIndex: number; 12 | pageSize: number; 13 | }; 14 | -------------------------------------------------------------------------------- /src/index.less: -------------------------------------------------------------------------------- 1 | @import '~nprogress/nprogress.css'; 2 | @import './styles/tool.less'; 3 | @import './styles/component.less'; 4 | 5 | @font-face { 6 | font-family: SourceHanSansSC-Bold; 7 | src: url('./assets/font/SourceHanSansSC-Bold.woff2'); 8 | } 9 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import reportWebVitals from './reportWebVitals'; 5 | import { Provider } from 'react-redux'; 6 | import store from './store'; 7 | import { BrowserRouter as Router } from 'react-router-dom'; 8 | import './locale'; 9 | import './utils/HighlightCode'; 10 | import './index.less'; 11 | 12 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | // If you want to start measuring performance in your app, pass a function 23 | // to log results (for example: reportWebVitals(console.log)) 24 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 25 | reportWebVitals(); 26 | -------------------------------------------------------------------------------- /src/locale/en-US/account.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: 'Account', 4 | pageDesc: 'You can view or change your personal information here', 5 | 6 | accountTitle: 'Basic account information', 7 | 8 | emailErrorMessage: { 9 | type: 'Please enter an email address in the correct format', 10 | match: 'The new email address cannot be the same as the old address', 11 | }, 12 | updateEmailSuccess: 'Email address updated successfully', 13 | }; 14 | -------------------------------------------------------------------------------- /src/locale/en-US/audit.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | result: '审核结果', 4 | passRage: '审核通过率', 5 | source: '审核结果评分', 6 | duplicate: '是否去重', 7 | downloadSql: '下载SQL语句', 8 | downloadReport: '下载审核报告', 9 | table: { 10 | number: '序号', 11 | auditLevel: '规则等级', 12 | auditStatus: '审核状态', 13 | auditResult: '审核结果', 14 | execSql: '执行语句', 15 | execStatus: '执行状态', 16 | execResult: '执行结果', 17 | rollback: '回滚语句', 18 | rollbackTips: '仅提示,不支持执行回滚', 19 | describe: '说明', 20 | analyze: '分析', 21 | }, 22 | 23 | filterForm: { 24 | highestAuditLevel: '最高审核告警等级', 25 | }, 26 | 27 | execStatus: { 28 | initialized: '准备执行', 29 | doing: '正在执行', 30 | succeeded: '执行成功', 31 | failed: '执行失败', 32 | manually_executed: '人工执行', 33 | }, 34 | 35 | auditStatus: { 36 | initialized: '准备审核', 37 | doing: '正在审核', 38 | finished: '审核完毕', 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /src/locale/en-US/dashboard.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: 'Dashboard', 4 | }; 5 | -------------------------------------------------------------------------------- /src/locale/en-US/index.ts: -------------------------------------------------------------------------------- 1 | import account from './account'; 2 | import audit from './audit'; 3 | import common from './common'; 4 | import dashboard from './dashboard'; 5 | import dataSource from './dataSource'; 6 | import login from './login'; 7 | import menu from './menu'; 8 | import order from './order'; 9 | import rule from './rule'; 10 | import ruleTemplate from './ruleTemplate'; 11 | import user from './user'; 12 | import whitelist from './whitelist'; 13 | import system from './system'; 14 | import reportStatistics from './reportStatistics'; 15 | import projectManage from './projectManage'; 16 | import syncDataSource from './syncDataSource' 17 | 18 | // eslint-disable-next-line import/no-anonymous-default-export 19 | export default { 20 | translation: { 21 | login, 22 | common, 23 | menu, 24 | user, 25 | dataSource, 26 | ruleTemplate, 27 | account, 28 | rule, 29 | audit, 30 | order, 31 | dashboard, 32 | whitelist, 33 | system, 34 | reportStatistics, 35 | projectManage, 36 | syncDataSource 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/locale/en-US/login.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | powered: 'Action SQLe', 4 | pageTitle: 'SQL Audit Platform', 5 | login: 'Login', 6 | }; 7 | -------------------------------------------------------------------------------- /src/locale/en-US/menu.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | dashboard: 'Dashboard', 4 | rule: 'Rule', 5 | platformManage: 'Platform Manage', 6 | user: 'User', 7 | dataSource: 'Database', 8 | ruleTemplate: 'Rule Template', 9 | progressManage: 'Progress', 10 | systemSetting: 'System Setting', 11 | workflow: 'Workflow', 12 | order: 'Order List', 13 | orderDetail: 'Order Detail', 14 | whitelist:'Whitelist' 15 | }; 16 | -------------------------------------------------------------------------------- /src/locale/en-US/rule.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: 'Instance Rules', 4 | pageDesc: 5 | '你可以在这里查看所有的审核规则,或者查看某一个数据源启用的所有审核规则', 6 | 7 | allRules: 'All Rules', 8 | instanceRuleList: 'Instance Rules List', 9 | activeRules: 'Enable rules of {{name}}', 10 | disableRules: 'Disable rules of {{name}}', 11 | form: { 12 | instance: 'Select Instance', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/locale/en-US/system.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: '系统设置', 4 | pageDesc: '您可以在这里配置您的邮箱SMTP等系统配置', 5 | }; 6 | -------------------------------------------------------------------------------- /src/locale/en-US/whitelist.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: 'Whitelist', 4 | }; 5 | -------------------------------------------------------------------------------- /src/locale/zh-CN/account.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: '个人中心', 4 | pageDesc: '您可以在这里查看或更改您的个人信息', 5 | 6 | accountTitle: '账户基本信息', 7 | 8 | emailErrorMessage: { 9 | type: '请输入正确格式的邮箱地址', 10 | match: '新邮箱地址不能与旧地址一致', 11 | }, 12 | phoneErrorMessage: { 13 | type: '请输入正确格式的手机号码', 14 | match: '新手机号码不能与旧号码一致', 15 | }, 16 | 17 | modifyPassword: { 18 | button: '修改密码', 19 | title: '修改当前用户密码', 20 | oldPassword: '旧密码', 21 | newPassword: '新密码', 22 | newPasswordConfirm: '确认新密码', 23 | }, 24 | 25 | updateEmailSuccess: '邮箱地址更新成功', 26 | updateWechatSuccess: '微信号更新成功', 27 | updatePhoneSuccess: '手机号更新成功', 28 | }; 29 | -------------------------------------------------------------------------------- /src/locale/zh-CN/audit.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | result: '审核结果', 4 | passRage: '审核通过率', 5 | source: '审核结果评分', 6 | duplicate: '是否去重', 7 | downloadSql: '下载SQL语句', 8 | downloadReport: '下载审核报告', 9 | table: { 10 | number: '序号', 11 | auditLevel: '规则等级', 12 | auditStatus: '审核状态', 13 | auditResult: '审核结果', 14 | execSql: '执行语句', 15 | execStatus: '执行状态', 16 | execResult: '执行结果', 17 | rollback: '回滚语句', 18 | rollbackTips: '仅提示,不支持执行回滚', 19 | describe: '说明', 20 | analyze: '分析', 21 | }, 22 | 23 | filterForm: { 24 | highestAuditLevel: '最高审核告警等级', 25 | }, 26 | 27 | execStatus: { 28 | initialized: '准备执行', 29 | doing: '正在执行', 30 | succeeded: '执行成功', 31 | failed: '执行失败', 32 | manually_executed: '人工执行', 33 | terminate_failed: '中止失败', 34 | terminate_succeeded: '中止成功', 35 | terminating: '正在中止', 36 | }, 37 | 38 | auditStatus: { 39 | initialized: '准备审核', 40 | doing: '正在审核', 41 | finished: '审核完毕', 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /src/locale/zh-CN/dashboard.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: '工作台', 4 | pageDesc: '您可以通过筛选项目, 查看项目对应的工作台数据', 5 | tableLimitTips: '当前表格仅展示前{{number}}条数据', 6 | allProjectTip: '所有项目', 7 | projectName: '项目名称', 8 | title: { 9 | pendingOrder: '待处理工单', 10 | myOrderSituation: '我的工单情况', 11 | recentlyOnlineWorkOrder: '近24h上线工单', 12 | }, 13 | pendingOrder: { 14 | needMeReview: '需审核', 15 | needMeExec: '需上线', 16 | }, 17 | myOrderSituation: { 18 | pendingReviewByMe: '待审核', 19 | pendingExecByMe: '待上线', 20 | rejectedOrderByMe: '被驳回', 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/locale/zh-CN/login.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | powered: 'SQLE', 4 | pageTitle: 'SQL审核平台', 5 | login: '登录', 6 | otherMethod: '其他登录方式', 7 | 8 | userAgreementTips: '已阅读并同意', 9 | userAgreement: '用户协议', 10 | 11 | errorMessage: { 12 | userAgreement: '请先阅读并同意用户协议', 13 | }, 14 | 15 | oauth: { 16 | title: '用户绑定', 17 | form: { 18 | username: '绑定的SQLE用户名', 19 | }, 20 | submitButton: '绑定并登录', 21 | bindTips: '如果用户名不存在,会自动创建', 22 | errorTitle: 'oauth登录错误', 23 | lostToken: '没有找到token,请返回登录页面重试', 24 | lostOauth2Token: '没有找到oauth token,请返回登录页面重试', 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/locale/zh-CN/menu.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | dashboard: 'Dashboard', 4 | rule: '规则', 5 | platformManage: '平台管理', 6 | userCenter: '用户中心', 7 | user: '用户管理', 8 | role: '角色管理', 9 | userGroup: '用户组管理', 10 | dataSource: '数据源', 11 | ruleManager: '规则管理', 12 | ruleTemplate: '规则模板', 13 | progressManage: '流程模板', 14 | systemSetting: '系统设置', 15 | workflow: '工单创建', 16 | order: '工单', 17 | orderList: '工单列表', 18 | orderDetail: '工单详情', 19 | whitelist: '白名单', 20 | auditPlane: '智能扫描', 21 | auditPlaneList: '扫描任务列表', 22 | sqlQuery: 'SQL工作台', 23 | orderSqlAnalyze: '工单SQL分析', 24 | auditPlanSqlAnalyze: '审核计划SQL分析', 25 | reportStatistics: '报表统计', 26 | projectManage: '项目', 27 | member: '成员', 28 | projectOverview: '项目概览', 29 | allInstanceType: '所有数据库类型', 30 | syncDataSource: '外部数据源同步', 31 | operationRecord: '操作记录', 32 | sqlManagement: 'SQL管控', 33 | sqlAudit: 'SQL审核', 34 | }; 35 | -------------------------------------------------------------------------------- /src/locale/zh-CN/operationRecord.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | list: { 4 | title: '操作记录列表', 5 | exportButtonText: '导出', 6 | exporting: '正在导出操作记录列表...', 7 | exportSuccessTips: '操作记录列表导出成功', 8 | filterForm: { 9 | operatingTime: '操作时间', 10 | projectName: '操作项目', 11 | operator: '操作人', 12 | operationType: '操作类型', 13 | operationAction: '操作内容', 14 | platformOperation: '-- (平台操作)', 15 | }, 16 | column: { 17 | operatingTime: '操作时间', 18 | operator: '操作人', 19 | operationType: '操作类型', 20 | operationAction: '操作内容', 21 | operationTarget: '对象', 22 | projectName: '项目', 23 | status: '状态', 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/locale/zh-CN/role.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | roleListTitle: '角色列表', 4 | 5 | createRole: { 6 | button: '创建角色', 7 | modalTitle: '创建角色', 8 | createSuccessTips: '创建角色 "{{name}}" 成功', 9 | }, 10 | 11 | updateRole: { 12 | modalTitle: '更新角色', 13 | updateSuccessTips: '更新角色 "{{name}}" 成功', 14 | }, 15 | 16 | deleteRole: { 17 | deleteTips: '确认要删除角色 "{{name}}"?', 18 | deleting: '正在删除角色 "{{name}}"...', 19 | deleteSuccessTips: '删除角色 "{{name}}" 成功', 20 | }, 21 | 22 | roleForm: { 23 | roleName: '角色名', 24 | roleDesc: '角色描述', 25 | databases: '数据源', 26 | usernames: '绑定用户', 27 | operationCodes: '动作权限', 28 | userGroups: '所属用户组', 29 | isDisabled: '禁用', 30 | }, 31 | 32 | roleListFilter: { 33 | usernamePlaceholder: '请输入要搜索的用户名', 34 | rolePlaceholder: '请输入要搜索的角色', 35 | databasePlaceholder: '请输入要搜索的数据源', 36 | }, 37 | 38 | roleList: { 39 | username: '绑定的用户', 40 | database: '绑定的数据源', 41 | userGroup: '绑定的用户组', 42 | operation: '拥有的动作权限', 43 | disabled: '是否禁用', 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /src/locale/zh-CN/rule.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: '查看规则', 4 | pageDesc: 5 | '你可以在这里查看所有的审核规则,或者查看某一个规则模板启用的所有审核规则', 6 | 7 | allRules: '全部规则', 8 | templateRuleList: '模板规则列表', 9 | activeRules: '模板{{name}}启用的规则', 10 | disableRules: '模板{{name}}禁用的规则', 11 | globalRuleTemplate: '全局规则模板', 12 | projectRuleTemplate: '项目规则模板', 13 | form: { 14 | project: '项目', 15 | ruleTemplate: '规则模板', 16 | dbType: '数据库类型', 17 | ruleTemplateTips: 18 | '当未选择项目时, 当前规则模板为全局规则模板, 选择后为项目下的规则模板', 19 | }, 20 | ruleLevelIcon: { 21 | normal: '普通', 22 | notice: '提示', 23 | warn: '告警', 24 | error: '错误', 25 | toolTipsTitle: '告警等级: {{ruleLevel}}({{text}})', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/locale/zh-CN/ruleKnowledge.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-anonymous-default-export */ 2 | export default { 3 | pageTitle: '知识库', 4 | pageDesc: '您可以在这里了解规则的更多信息, 并进行沉淀', 5 | 6 | ruleUnderstanding: '规则理解', 7 | edit: '编辑', 8 | noData: '暂无数据, 请编辑', 9 | hasDirtyDataTips: '当前内容已经发生更改,是否确认取消修改?', 10 | successTips: '规则理解修改成功', 11 | }; 12 | -------------------------------------------------------------------------------- /src/locale/zh-CN/ruleManager.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: '规则管理', 4 | pageDesc: '您可以在这里配置默认规则模板以及自定义规则', 5 | }; 6 | -------------------------------------------------------------------------------- /src/locale/zh-CN/sqlAnalyze.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: 'SQL分析', 4 | pageDesc: '你可以在这里查看执行的sql语句的解析', 5 | 6 | sqlExplain: 'SQL解析', 7 | tableTitle: '{{tableName}}表', 8 | }; 9 | -------------------------------------------------------------------------------- /src/locale/zh-CN/sqlQuery.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: 'SQL工作台', 4 | pageDescribe: '你可以对你有操作权限的数据源进行受审核管控的SQL操作', 5 | eeErrorTips: '该环境未配置SQL查询,请设置后再使用', 6 | eeErrorTips2: '配置方式及使用说明请查看使用文档', 7 | 8 | jumpToCloudbeaver: '打开SQL工作台', 9 | 10 | databaseTables: { 11 | title: '数据库表', 12 | tabTitle: '{{tableName}}结构', 13 | columns: '列信息', 14 | index: '索引信息', 15 | createdTableSql: '建表语句', 16 | }, 17 | executeResult: { 18 | title: '执行结果', 19 | errorMessageTitle: '查询出现错误', 20 | resultTitle: '结果{{index}}', 21 | paginationInfo: 22 | '当前数据为第{{current_page}}页, 显示第{{start_line}}至{{end_line}}条记录, 查询耗时{{execution_time}}ms', 23 | }, 24 | executePlan: { 25 | title: '执行计划{{index}}', 26 | sql: 'SQL语句', 27 | sqlExplain: '执行计划', 28 | performanceStatistics: '性能统计', 29 | affectRows: '影响行数', 30 | affectRowTips: '区别于执行计划的rows列,显示SQL的实际影响行数', 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/locale/zh-CN/userGroup.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | userGroupField: { 4 | userGroupName: '用户组名', 5 | userGroupDesc: '用户组描述', 6 | isDisabled: '禁用', 7 | isDisabledTips: 8 | '当用户组被禁用,组内用户不会被禁用,但会失去该用户组所关联的数据源及对应角色权限', 9 | userNameList: '绑定用户', 10 | }, 11 | 12 | userGroupState: { 13 | disabled: '禁用', 14 | normal: '正常', 15 | }, 16 | 17 | userGroupList: { 18 | title: '用户组列表', 19 | 20 | isDisabled: '是否禁用', 21 | }, 22 | 23 | createUserGroup: { 24 | title: '创建用户组', 25 | successTips: '创建用户组 "{{name}}" 成功', 26 | }, 27 | 28 | updateUserGroup: { 29 | title: '编辑用户组', 30 | successTips: '编辑用户组 "{{name}}" 成功', 31 | }, 32 | 33 | deleteUserGroup: { 34 | confirm: '确认要删除用户组: "{{name}}" 吗?', 35 | deleting: '正在删除用户组: "{{name}}" ...', 36 | deleteSuccess: '删除用户组: "{{name}}" 成功', 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/locale/zh-CN/whitelist.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-anonymous-default-export 2 | export default { 3 | pageTitle: '白名单', 4 | pageDesc: 5 | '您可以在这里添加一些SQL语句,这些SQL语句在进行审核的时候不会触发任何审核规则。', 6 | 7 | ceTips: 8 | '如果用户开启了某条规则,但在实际使用中又想临时规避某些规则的触发,可以启用平台的白名单功能。\n目前支持按字符串匹配或按照SQL指纹匹配,添加在SQL审核白名单中的语句,在提交工单申请时,将不受审核规则的约束。', 9 | allWhitelist: '所有白名单语句', 10 | table: { 11 | sql: 'SQL语句', 12 | desc: '白名单描述', 13 | matchType: '匹配模式', 14 | }, 15 | 16 | matchType: { 17 | exact: '字符串匹配', 18 | fingerPrint: 'sql指纹匹配', 19 | }, 20 | 21 | operate: { 22 | addWhitelist: '添加白名单', 23 | 24 | deleting: '正在删除白名单语句...', 25 | deleteSuccess: '删除白名单语句成功', 26 | confirmDelete: '确认删除这条白名单么?', 27 | }, 28 | 29 | modal: { 30 | add: { 31 | title: '添加白名单', 32 | }, 33 | update: { 34 | title: '更新白名单', 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /src/page/Account/Modal/ModifyPassword/index.type.ts: -------------------------------------------------------------------------------- 1 | import { ModalName } from '../../../../data/ModalName'; 2 | 3 | export type ModifyPasswordProps = { 4 | visible: boolean; 5 | setModalStatus: (modalName: ModalName, status: boolean) => void; 6 | }; 7 | 8 | export type ModifyPasswordFormFields = { 9 | password: string; 10 | newPassword: string; 11 | newPasswordConfirm: string; 12 | }; 13 | -------------------------------------------------------------------------------- /src/page/Account/UserEmail/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IUserDetailResV1 } from '../../../api/common.d'; 2 | 3 | export type UserEmailProps = { 4 | refreshUserInfo: () => void; 5 | userInfo?: IUserDetailResV1; 6 | }; 7 | 8 | export interface UseInputChangeParams extends UserEmailProps { 9 | closeEditEmail: () => void; 10 | setInputValue: (value: string) => void; 11 | } 12 | -------------------------------------------------------------------------------- /src/page/Account/UserPhone/index.tsx: -------------------------------------------------------------------------------- 1 | import { IUserDetailResV1 } from '../../../api/common'; 2 | 3 | export type UserPhoneProps = { 4 | refreshUserInfo: () => void; 5 | userInfo?: IUserDetailResV1; 6 | }; 7 | -------------------------------------------------------------------------------- /src/page/Account/Wechat/index.tsx: -------------------------------------------------------------------------------- 1 | import { IUserDetailResV1 } from '../../../api/common'; 2 | 3 | export type WechatProps = { 4 | refreshUserInfo: () => void; 5 | userInfo?: IUserDetailResV1; 6 | }; 7 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanDetail/Detail/Report/index.type.tsx: -------------------------------------------------------------------------------- 1 | export type AuditPlanReportUrlParams = { 2 | auditPlanName: string; 3 | reportId: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanDetail/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`PlanDetail should match snapshot 1`] = ` 4 | 5 | , 9 | ] 10 | } 11 | ghost={false} 12 | style={ 13 | Object { 14 | "marginBottom": 24, 15 | } 16 | } 17 | title="auditPlan.detailPage.pageTitle" 18 | > 19 | 20 | auditPlan.detailPage.auditTaskType 21 | 22 | auditPlan.detailPage.pageDesc 23 | 24 | 25 | 26 | `; 27 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanDetail/index.type.ts: -------------------------------------------------------------------------------- 1 | export type PlanDetailUrlParams = { 2 | auditPlanName: string; 3 | }; 4 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanForm/AuditTaskType/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IAuditPlanParamResV1, IAuditPlanResV1 } from '../../../../api/common'; 3 | 4 | export type AuditTaskTypeProps = { 5 | dbType: string; 6 | form: FormInstance; 7 | defaultValue?: IAuditPlanResV1; 8 | updateCurrentTypeParams?: (params?: IAuditPlanParamResV1[]) => void; 9 | }; 10 | 11 | export { default as AuditTaskType } from './AuditTaskType'; 12 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanForm/DataSource/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IAuditPlanResV1 } from '../../../../api/common'; 3 | import { PlanFormField } from '../index.type'; 4 | 5 | export type DataSourceProps = { 6 | form: FormInstance; 7 | dataSource: string; 8 | defaultValue?: IAuditPlanResV1; 9 | dbTypeChange?: (dbType: string) => void; 10 | dataSourceChange?: (dataSource: string) => void; 11 | projectName: string; 12 | }; 13 | 14 | export { default as DataSource } from './DataSource'; 15 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanForm/__testData__/auditMeta.ts: -------------------------------------------------------------------------------- 1 | export const auditTaskMetas = [ 2 | { 3 | audit_plan_params: [ 4 | { desc: '字段a', key: 'a', type: 'string', value: '123' }, 5 | { desc: '字段b', key: 'b', type: 'int', value: '123' }, 6 | { desc: '字段c', key: 'c', type: 'bool', value: 'true' }, 7 | ], 8 | audit_plan_type: 'normal', 9 | audit_plan_type_desc: '普通的SQL审核', 10 | instance_type: 'mysql', 11 | }, 12 | { 13 | audit_plan_params: [ 14 | { desc: '字段d', key: 'd', type: 'string', value: '123' }, 15 | { desc: '字段e', key: 'e', type: 'int', value: '123' }, 16 | { desc: '字段f', key: 'f', type: 'bool', value: 'true' }, 17 | ], 18 | audit_plan_type: 'slowLog', 19 | audit_plan_type_desc: '慢日志审核', 20 | instance_type: 'mysql', 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IAuditPlanResV1 } from '../../../api/common'; 3 | import { FormItem } from '../../../components/BackendForm'; 4 | 5 | export type PlanFormField = { 6 | name: string; 7 | databaseName?: string; 8 | schema?: string; 9 | dbType: string; 10 | cron: string; 11 | auditTaskType: string; 12 | ruleTemplateName?: string; 13 | params?: { 14 | [key: string]: string | boolean; 15 | }; 16 | asyncParams?: FormItem[]; 17 | }; 18 | 19 | export type PlanFormProps = { 20 | submit: (data: PlanFormField) => Promise; 21 | defaultValue?: IAuditPlanResV1; 22 | projectName: string; 23 | form: FormInstance; 24 | }; 25 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanList/Modal/SubscribeNotice/index.data.tsx: -------------------------------------------------------------------------------- 1 | import i18n from '../../../../../locale'; 2 | 3 | export const webhooksTemplateDefault = `{ 4 | "msg_type": "text", 5 | "content": { 6 | "text":"{{.subject}} \\n {{.body}}" 7 | } 8 | }`; 9 | 10 | export const variableData: Array<{ 11 | name: string; 12 | variable: string; 13 | }> = [ 14 | { 15 | name: i18n.t( 16 | 'auditPlan.subscribeNotice.form.webhooksTemplateHelp.variable.subject' 17 | ), 18 | variable: '{{.subject}}', 19 | }, 20 | { 21 | name: i18n.t( 22 | 'auditPlan.subscribeNotice.form.webhooksTemplateHelp.variable.body' 23 | ), 24 | variable: '{{.body}}', 25 | }, 26 | ]; 27 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanList/Modal/SubscribeNotice/index.type.ts: -------------------------------------------------------------------------------- 1 | import { UpdateAuditPlanNotifyConfigReqV1NotifyLevelEnum } from '../../../../../api/common.enum'; 2 | 3 | export type SubscribeNoticeFormFields = { 4 | interval: number; 5 | level: UpdateAuditPlanNotifyConfigReqV1NotifyLevelEnum; 6 | emailEnable: boolean; 7 | webhooksEnable: boolean; 8 | webhooksUrl: string; 9 | template: string; 10 | }; 11 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanList/Modal/SubscribeNotice/tests/__testData__/index.ts: -------------------------------------------------------------------------------- 1 | export const auditPlanSubscribeNoticeConfig = { 2 | enable_email_notify: true, 3 | enable_web_hook_notify: true, 4 | notify_interval: 6, 5 | notify_level: 'error', 6 | web_hook_template: 7 | '{\n "msg_type": "text2",\n "bbb": {\n "aaa":"{{.subject}} \\n {{.body}}"\n }\n }', 8 | web_hook_url: 'prospero://kdmgujzl.tw/kgkredqxr', 9 | }; 10 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanList/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import SubscribeNotice from './SubscribeNotice'; 2 | 3 | const PlanListModal = () => { 4 | return ( 5 | <> 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default PlanListModal; 12 | -------------------------------------------------------------------------------- /src/page/AuditPlan/PlanList/PlanListFilterForm/index.type.ts: -------------------------------------------------------------------------------- 1 | export type PlanListFilterFormFields = { 2 | filter_audit_plan_db_type?: string; 3 | fuzzy_search_audit_plan_name?: string; 4 | filter_audit_plan_type?: string; 5 | filter_audit_plan_instance_name?: string; 6 | }; 7 | 8 | export type PlanListFilterFormProps = { 9 | submit?: (values: PlanListFilterFormFields) => void; 10 | projectName: string; 11 | }; 12 | -------------------------------------------------------------------------------- /src/page/AuditPlan/UpdatePlan/index.type.ts: -------------------------------------------------------------------------------- 1 | export type UpdateAuditPlanUrlParams = { 2 | auditPlanName: string; 3 | }; 4 | -------------------------------------------------------------------------------- /src/page/AuditPlan/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AuditPlan should match snapshot 1`] = ` 4 | 5 | 9 | auditPlan.pageDesc 10 | 11 |
14 | 15 |
16 |
17 | `; 18 | -------------------------------------------------------------------------------- /src/page/AuditPlan/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import toJSON from 'enzyme-to-json'; 3 | import AuditPlan from '.'; 4 | 5 | describe('AuditPlan', () => { 6 | test('should match snapshot', () => { 7 | const wrapper = shallow(); 8 | expect(toJSON(wrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/AuditPlan/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { Outlet } from 'react-router-dom'; 4 | 5 | const AuditPlan = () => { 6 | const { t } = useTranslation(); 7 | return ( 8 | <> 9 | 10 | {t('auditPlan.pageDesc')} 11 | 12 | 13 |
14 | 15 |
16 | 17 | ); 18 | }; 19 | 20 | export default AuditPlan; 21 | -------------------------------------------------------------------------------- /src/page/BindUser/index.type.ts: -------------------------------------------------------------------------------- 1 | export type OauthBindUserUrlParams = { 2 | user_exist?: string; 3 | sqle_token?: string; 4 | oauth2_user_id?: string; 5 | error?: string; 6 | }; 7 | 8 | export type OauthLoginFormFields = { 9 | username: string; 10 | password: string; 11 | }; 12 | -------------------------------------------------------------------------------- /src/page/CustomRule/CustomRuleForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { ICustomRuleResV1 } from '../../../api/common'; 3 | import { CustomRuleResV1LevelEnum } from '../../../api/common.enum'; 4 | 5 | export type CustomRuleFormBaseInfoFields = { 6 | annotation: string; 7 | desc: string; 8 | dbType: string; 9 | ruleType: string; 10 | level?: CustomRuleResV1LevelEnum; 11 | }; 12 | 13 | export type EditRuleScriptFields = { 14 | script: string; 15 | }; 16 | 17 | export type CustomRuleFormProps = { 18 | form: FormInstance; 19 | editScriptForm: FormInstance; 20 | defaultData?: ICustomRuleResV1; 21 | step: number; 22 | prevStep: () => void; 23 | submit: () => void; 24 | baseInfoSubmit: () => void; 25 | children: React.ReactNode; 26 | submitLoading: boolean; 27 | }; 28 | 29 | export type BaseInfoFormProps = { 30 | submit: () => void; 31 | } & Pick; 32 | 33 | export type EditRuleScriptProps = { 34 | form: FormInstance; 35 | } & Pick< 36 | CustomRuleFormProps, 37 | 'prevStep' | 'submit' | 'submitLoading' | 'defaultData' 38 | >; 39 | -------------------------------------------------------------------------------- /src/page/CustomRule/CustomRuleList/index.tsx: -------------------------------------------------------------------------------- 1 | import CustomRuleList from './CustomRuleList'; 2 | 3 | export type FilterFormAndCreateButtonProps = { 4 | getCustomRuleList: (dbType: string, ruleName: string) => void; 5 | }; 6 | 7 | export default CustomRuleList; 8 | -------------------------------------------------------------------------------- /src/page/CustomRule/__mockApi__/data.ts: -------------------------------------------------------------------------------- 1 | import { ICustomRuleResV1 } from '../../../api/common'; 2 | import { CustomRuleResV1LevelEnum } from '../../../api/common.enum'; 3 | 4 | export const customRules: ICustomRuleResV1[] = [ 5 | { 6 | db_type: 'mysql', 7 | annotation: 'desc desc desc', 8 | level: CustomRuleResV1LevelEnum.notice, 9 | rule_id: '1', 10 | desc: 'name1', 11 | rule_script: 'w+', 12 | type: 'rule_type1', 13 | }, 14 | { 15 | db_type: 'oracle', 16 | annotation: 'desc desc desc', 17 | level: CustomRuleResV1LevelEnum.error, 18 | rule_id: '2', 19 | desc: 'name2', 20 | rule_script: 'w+', 21 | type: 'rule_type2', 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /src/page/CustomRule/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`CustomRule should match snapshot 1`] = ` 4 | 5 | } 7 | path="custom" 8 | /> 9 | } 11 | path="custom/create" 12 | /> 13 | } 15 | path="custom/update/:ruleID" 16 | /> 17 | 18 | `; 19 | -------------------------------------------------------------------------------- /src/page/CustomRule/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import RuleTemplate from '.'; 3 | import toJson from 'enzyme-to-json'; 4 | 5 | describe('CustomRule', () => { 6 | test('should match snapshot', () => { 7 | const wrapper = shallow(); 8 | expect(toJson(wrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/CustomRule/index.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | import { Route, Routes } from 'react-router-dom'; 3 | 4 | const CustomRuleList = lazy(() => import('./CustomRuleList')); 5 | const UpdateCustomRule = lazy(() => import('./UpdateCustomRule')); 6 | const CreateCustomRule = lazy(() => import('./CreateCustomRule')); 7 | 8 | const CustomRule: React.FC = () => { 9 | return ( 10 | 11 | } /> 12 | } /> 13 | } /> 14 | 15 | ); 16 | }; 17 | 18 | export default CustomRule; 19 | -------------------------------------------------------------------------------- /src/page/DataSource/DataSourceForm/MaintenanceTimePicker/MaintenanceTimePicker.less: -------------------------------------------------------------------------------- 1 | .maintenance-time-picker-popover-wrapper { 2 | width: 300px; 3 | .maintenance-time-picker-slider { 4 | width: 90%; 5 | } 6 | .maintenance-time-picker-btn-wrapper { 7 | text-align: right; 8 | margin-top: 5px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/page/DataSource/DataSourceForm/MaintenanceTimePicker/index.tsx: -------------------------------------------------------------------------------- 1 | import MaintenanceTimePicker from './MaintenanceTimePicker'; 2 | 3 | export type MaintenanceTimeValue = { 4 | startTime: { 5 | hour: number; 6 | minute: number; 7 | }; 8 | endTime: { 9 | hour: number; 10 | minute: number; 11 | }; 12 | }; 13 | 14 | export type MaintenanceTimePickerProps = { 15 | value?: MaintenanceTimeValue[]; 16 | onChange?: (values: MaintenanceTimeValue[]) => void; 17 | }; 18 | 19 | export default MaintenanceTimePicker; 20 | -------------------------------------------------------------------------------- /src/page/DataSource/DataSourceForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IInstanceResV1 } from '../../../api/common'; 3 | import { SQLQueryConfigReqV1AllowQueryWhenLessThanAuditLevelEnum } from '../../../api/common.enum'; 4 | import { 5 | BackendFormRequestParams, 6 | BackendFormValues, 7 | } from '../../../components/BackendForm'; 8 | import { MaintenanceTimeValue } from './MaintenanceTimePicker'; 9 | 10 | export type DataSourceFormField = { 11 | name: string; 12 | describe?: string; 13 | ip: string; 14 | port: number; 15 | user: string; 16 | password: string; 17 | ruleTemplate?: string; 18 | type?: string; 19 | params?: BackendFormValues; 20 | asyncParams?: BackendFormRequestParams[]; 21 | maintenanceTime: MaintenanceTimeValue[]; 22 | needAuditForSqlQuery?: boolean; 23 | allowQueryWhenLessThanAuditLevel?: SQLQueryConfigReqV1AllowQueryWhenLessThanAuditLevelEnum; 24 | }; 25 | 26 | export type IDataSourceFormProps = { 27 | form: FormInstance; 28 | defaultData?: IInstanceResV1; 29 | submit?: (values: DataSourceFormField) => Promise; 30 | projectName: string; 31 | }; 32 | -------------------------------------------------------------------------------- /src/page/DataSource/DataSourceList/DataSourceListFilterForm/index.type.ts: -------------------------------------------------------------------------------- 1 | export type DataSourceListFilterFields = { 2 | filter_instance_name?: string | undefined; 3 | filter_db_type?: string | undefined; 4 | filter_db_host?: string | undefined; 5 | filter_db_port?: string | undefined; 6 | filter_rule_template_name?: string | undefined; 7 | }; 8 | 9 | export interface DataSourceListFilterFormProps { 10 | submit: (values: DataSourceListFilterFields) => void; 11 | projectName: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/page/DataSource/UpdateDataSource/index.type.ts: -------------------------------------------------------------------------------- 1 | export type UpdateDataSourceUrlParams = { 2 | instanceName: string; 3 | }; 4 | -------------------------------------------------------------------------------- /src/page/DataSource/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`DataSource should render page title and page describe 1`] = ` 4 |
7 | 11 | dataSource.pageDesc 12 | 13 |
16 | 17 |
18 |
19 | `; 20 | -------------------------------------------------------------------------------- /src/page/DataSource/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import toJson from 'enzyme-to-json'; 3 | import DataSource from '.'; 4 | 5 | describe('DataSource', () => { 6 | test('should render page title and page describe', () => { 7 | const wrapper = shallow(); 8 | expect(toJson(wrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/DataSource/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { Outlet } from 'react-router-dom'; 4 | 5 | const DataSource = () => { 6 | const { t } = useTranslation(); 7 | 8 | return ( 9 |
10 | 11 | {t('dataSource.pageDesc')} 12 | 13 |
14 | 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default DataSource; 21 | -------------------------------------------------------------------------------- /src/page/DataSource/tool/index.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | turnCommonToDataSourceParams, 3 | turnDataSourceAsyncFormToCommon, 4 | } from '.'; 5 | 6 | describe('datasource tool', () => { 7 | it('should turn data source async params to common', () => { 8 | expect( 9 | turnDataSourceAsyncFormToCommon([ 10 | { 11 | description: '234', 12 | name: '124', 13 | type: 'int', 14 | value: '123', 15 | }, 16 | ]) 17 | ).toEqual([ 18 | { 19 | desc: '234', 20 | key: '124', 21 | type: 'int', 22 | value: '123', 23 | }, 24 | ]); 25 | }); 26 | 27 | it('should turn async form value to data source params', () => { 28 | expect( 29 | turnCommonToDataSourceParams([ 30 | { 31 | key: '124', 32 | value: '123', 33 | }, 34 | ]) 35 | ).toEqual([ 36 | { 37 | name: '124', 38 | value: '123', 39 | }, 40 | ]); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/page/DataSource/tool/index.ts: -------------------------------------------------------------------------------- 1 | export const turnDataSourceAsyncFormToCommon = < 2 | T extends { 3 | description?: string; 4 | name?: string; 5 | type?: string; 6 | value?: string; 7 | }[] 8 | >( 9 | data: T 10 | ): Array<{ 11 | desc?: string; 12 | key?: string; 13 | type?: string; 14 | value?: string; 15 | }> => { 16 | return data.map((item) => ({ 17 | desc: item.description, 18 | key: item.name, 19 | type: item.type, 20 | value: item.value, 21 | })); 22 | }; 23 | 24 | export const turnCommonToDataSourceParams = < 25 | T extends { 26 | key?: string; 27 | value?: string; 28 | }[] 29 | >( 30 | data: T 31 | ): Array<{ 32 | name?: string; 33 | value?: string; 34 | }> => { 35 | return data.map((item) => ({ 36 | name: item.key, 37 | value: item.value, 38 | })); 39 | }; 40 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/ImportRuleTemplate/index.tsx: -------------------------------------------------------------------------------- 1 | import ImportRuleTemplate from './ImportRuleTemplate'; 2 | 3 | export type SelectFileFormFields = { 4 | ruleTemplateFile: any; 5 | }; 6 | 7 | export default ImportRuleTemplate; 8 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/RuleTemplateForm/BaseInfoForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IRuleTemplateDetailResV1 } from '../../../../api/common'; 3 | import { RuleTemplateFormProps } from '../index.type'; 4 | 5 | export type RuleTemplateBaseInfoFields = { 6 | templateName: string; 7 | templateDesc?: string; 8 | db_type: string; 9 | }; 10 | 11 | export type RuleTemplateBaseInfoFormProps = { 12 | form: FormInstance; 13 | defaultData?: IRuleTemplateDetailResV1; 14 | submit: () => void; 15 | mode: RuleTemplateFormProps['mode']; 16 | }; 17 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/RuleTemplateForm/RuleSelect/RuleManagerModal/index.less: -------------------------------------------------------------------------------- 1 | .rule-manager-modal { 2 | .ant-input-disabled { 3 | background: transparent; 4 | border: none; 5 | box-shadow: none; 6 | padding-left: 0; 7 | cursor: default; 8 | } 9 | textarea { 10 | resize: none; 11 | } 12 | .wrap-form-item { 13 | .ant-form-item-label { 14 | white-space: pre-wrap; 15 | line-height: 14px; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/RuleTemplateForm/RuleSelect/RuleManagerModal/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IRuleResV1 } from '../../../../../api/common'; 2 | import { RuleResV1LevelEnum } from '../../../../../api/common.enum'; 3 | 4 | export type RuleManagerFormProps = { 5 | visible: boolean; 6 | ruleData?: IRuleResV1 | undefined; 7 | setVisibleFalse: () => void; 8 | submit: (values: IRuleResV1) => void; 9 | }; 10 | 11 | export interface IRuleManagerForm { 12 | rule_name: string; 13 | desc: string; 14 | annotation: string; 15 | type: string; 16 | db_type: string; 17 | level?: RuleResV1LevelEnum; 18 | params: Record; 19 | } 20 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/RuleTemplateForm/RuleSelect/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IRuleResV1 } from '../../../../api/common'; 2 | 3 | export type RuleSelectProps = { 4 | listLoading: boolean; 5 | allRules: IRuleResV1[]; 6 | activeRule: IRuleResV1[]; 7 | updateActiveRule: (value: IRuleResV1[]) => void; 8 | }; 9 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/RuleTemplateForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IRuleResV1, IRuleTemplateDetailResV1 } from '../../../api/common'; 3 | import { RuleTemplateBaseInfoFields } from './BaseInfoForm/index.type'; 4 | 5 | export type RuleTemplateFormProps = { 6 | form: FormInstance; 7 | defaultData?: IRuleTemplateDetailResV1; 8 | activeRule: IRuleResV1[]; 9 | allRules: IRuleResV1[]; 10 | ruleListLoading: boolean; 11 | submitLoading: boolean; 12 | step: number; 13 | updateActiveRule: (value: IRuleResV1[]) => void; 14 | baseInfoSubmit: () => void; 15 | prevStep: () => void; 16 | submit: () => void; 17 | mode: 'import' | 'update' | 'create'; 18 | children: React.ReactNode; 19 | }; 20 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/RuleTemplateList/Modal/CloneRuleTemplate/index.type.ts: -------------------------------------------------------------------------------- 1 | export type CloneRuleTemplateFormFields = { 2 | templateName: string; 3 | templateDesc?: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/RuleTemplateList/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import CloneRuleTemplateModal from './CloneRuleTemplate'; 2 | 3 | const RuleTemplateListModal = () => { 4 | return ; 5 | }; 6 | 7 | export default RuleTemplateListModal; 8 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`RuleTemplate should match snapshot 1`] = ` 4 | 5 | } 7 | path="template" 8 | /> 9 | } 11 | path="template/create" 12 | /> 13 | } 15 | path="template/import" 16 | /> 17 | } 19 | path="template/update/:templateName" 20 | /> 21 | 22 | `; 23 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import RuleTemplate from '.'; 3 | import toJson from 'enzyme-to-json'; 4 | 5 | describe('RuleTemplate', () => { 6 | test('should match snapshot', () => { 7 | const wrapper = shallow(); 8 | expect(toJson(wrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/GlobalRuleTemplate/index.tsx: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from 'react-router-dom'; 2 | import { lazy } from 'react'; 3 | 4 | const GlobalRuleTemplateList = lazy(() => import('./RuleTemplateList')); 5 | const GlobalImportRuleTemplate = lazy(() => import('./ImportRuleTemplate')); 6 | const GlobalUpdateRuleTemplate = lazy(() => import('./UpdateRuleTemplate')); 7 | const GlobalCreateRuleTemplate = lazy(() => import('./CreateRuleTemplate')); 8 | 9 | const GlobalRuleTemplate = () => { 10 | return ( 11 | 12 | } /> 13 | } /> 14 | } /> 15 | } 18 | /> 19 | 20 | ); 21 | }; 22 | 23 | export default GlobalRuleTemplate; 24 | -------------------------------------------------------------------------------- /src/page/Home/CommonTable/Table.tsx: -------------------------------------------------------------------------------- 1 | import { Result, Table } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { ICommonTableProps } from '.'; 4 | import { commonColumn } from './column'; 5 | 6 | const CommonTable: React.FC = ({ 7 | tableInfo, 8 | customColumn, 9 | }) => { 10 | const { t } = useTranslation(); 11 | return ( 12 | <> 13 | 26 | ) : undefined, 27 | }} 28 | dataSource={tableInfo.data ?? []} 29 | columns={ 30 | typeof customColumn === 'function' ? customColumn() : commonColumn() 31 | } 32 | /> 33 | 34 | ); 35 | }; 36 | 37 | export default CommonTable; 38 | -------------------------------------------------------------------------------- /src/page/Home/CommonTable/index.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from 'antd'; 2 | import { IWorkflowDetailResV1 } from '../../../api/common'; 3 | import { TableColumn } from '../../../types/common.type'; 4 | import CommonTable from './Table'; 5 | 6 | export const genTabPaneTitle = (title: string, badgeCount?: number) => { 7 | return badgeCount ? ( 8 | 14 | {title} 15 | 16 | ) : ( 17 | title 18 | ); 19 | }; 20 | 21 | export type CommonTableInfoType = { 22 | data: IWorkflowDetailResV1[]; 23 | error: Error | undefined; 24 | loading: boolean; 25 | }; 26 | 27 | export interface ICommonTableProps { 28 | tableInfo: CommonTableInfoType; 29 | customColumn?: () => TableColumn; 30 | } 31 | 32 | export const DASHBOARD_COMMON_GET_ORDER_NUMBER = 5; 33 | 34 | export default CommonTable; 35 | -------------------------------------------------------------------------------- /src/page/Home/DBAPanel/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IWorkflowStatisticsResV1 } from '../../../api/common'; 2 | 3 | export interface IDBAPanelProps { 4 | workflowStatistics?: IWorkflowStatisticsResV1; 5 | getWorkflowStatistics: () => void; 6 | projectName: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/page/Home/DEVPanel/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IWorkflowStatisticsResV1 } from '../../../api/common'; 2 | 3 | export interface IDEVPanelProps { 4 | workflowStatistics?: IWorkflowStatisticsResV1; 5 | getWorkflowStatistics: () => void; 6 | projectName: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/page/Home/RecentlyOrderPanel/index.type.ts: -------------------------------------------------------------------------------- 1 | export interface IRecentlyOrderPanelProps { 2 | projectName: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/page/Home/index.less: -------------------------------------------------------------------------------- 1 | .sqle-home-content { 2 | .tabs-panel-badge { 3 | color: inherit; 4 | } 5 | .tabs-panel-title { 6 | margin: 10px 16px 0 0; 7 | } 8 | .ant-card-head-title { 9 | padding: 16px 0 6px; 10 | } 11 | .tab-panel-title { 12 | font-size: 16px; 13 | font-weight: 500; 14 | margin-right: 16px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/page/Home/useDashboardRequest.ts: -------------------------------------------------------------------------------- 1 | import { useRequest } from 'ahooks'; 2 | import { ALL_PROJECT_NAME } from '.'; 3 | import workflow from '../../api/workflow'; 4 | import { 5 | IGetGlobalWorkflowsV1Params, 6 | IGetWorkflowsV1Params, 7 | } from '../../api/workflow/index.d'; 8 | 9 | const isGlobalParams = (value: unknown): value is IGetGlobalWorkflowsV1Params => 10 | (value as any).project_name === ALL_PROJECT_NAME; 11 | 12 | const useDashboardRequest = ( 13 | params: IGetWorkflowsV1Params | IGetGlobalWorkflowsV1Params, 14 | refreshDeps: React.DependencyList 15 | ) => { 16 | const request = isGlobalParams(params) 17 | ? () => workflow.getGlobalWorkflowsV1(params).then((res) => res.data.data) 18 | : () => workflow.getWorkflowsV1(params).then((res) => res.data.data); 19 | const response = useRequest(request, { 20 | refreshDeps, 21 | }); 22 | return response; 23 | }; 24 | 25 | export default useDashboardRequest; 26 | -------------------------------------------------------------------------------- /src/page/Member/Common/__test__/__snapshots__/renderRolesInfo.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test renderRolesInfo should match snapshot 1`] = ` 4 | 7 |
8 | db1: [ test1,test2,test3 ] 9 |
10 |
11 | db2: [ test3 ] 12 |
13 |
14 | db3: [ test3,test4,test5,test6,test7 ] 15 |
16 |
17 | `; 18 | 19 | exports[`test renderRolesInfo should match snapshot 2`] = ` 20 | 23 | 26 | db1: [ test1,test2,test3 ] 27 | 28 | 31 | db2: [ test3 ] 32 | 33 | 36 | db3: [ test3,test4,test5,test6,test7 ] 37 | 38 | 39 | `; 40 | -------------------------------------------------------------------------------- /src/page/Member/Common/__test__/renderRolesInfo.test.tsx: -------------------------------------------------------------------------------- 1 | import { IBindRoleReqV1 } from '../../../../api/common'; 2 | import renderRolesInfo from '../renderRolesInfo'; 3 | 4 | describe('test renderRolesInfo', () => { 5 | const roles: IBindRoleReqV1[] = [ 6 | { 7 | instance_name: 'db1', 8 | role_names: ['test1', 'test2', 'test3'], 9 | }, 10 | { 11 | instance_name: 'db2', 12 | role_names: ['test3'], 13 | }, 14 | { 15 | instance_name: 'db3', 16 | role_names: ['test3', 'test4', 'test5', 'test6', 'test7'], 17 | }, 18 | ]; 19 | test('should match snapshot', () => { 20 | expect(renderRolesInfo(roles, false)).toMatchSnapshot(); 21 | expect(renderRolesInfo(roles, true)).toMatchSnapshot(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/page/Member/Common/renderRolesInfo.tsx: -------------------------------------------------------------------------------- 1 | import { Space, Typography } from 'antd'; 2 | import { IBindRoleReqV1 } from '../../../api/common'; 3 | 4 | const renderRolesInfo = (roles: IBindRoleReqV1[], ellipsis: boolean) => { 5 | const genContent = (roleNames?: string[], instanceName?: string) => 6 | `${instanceName ?? ''}: [ ${roleNames?.toString() ?? ''} ]`; 7 | return ( 8 | 9 | {roles.map((v) => { 10 | return ellipsis ? ( 11 | 12 | {genContent(v.role_names, v.instance_name)} 13 | 14 | ) : ( 15 |
16 | {genContent(v.role_names, v.instance_name)} 17 |
18 | ); 19 | })} 20 |
21 | ); 22 | }; 23 | 24 | export default renderRolesInfo; 25 | -------------------------------------------------------------------------------- /src/page/Member/MemberGroupList/index.type.ts: -------------------------------------------------------------------------------- 1 | export type MemberGroupListFilterFormFields = { 2 | filterUserGroupName?: string; 3 | filterInstance?: string; 4 | }; 5 | 6 | export type MemberGroupListFilterFormProps = { 7 | submit: (values: MemberGroupListFilterFormFields) => void; 8 | }; 9 | -------------------------------------------------------------------------------- /src/page/Member/MemberList/__test__/utils.ts: -------------------------------------------------------------------------------- 1 | import user from '../../../../api/user'; 2 | import { resolveThreeSecond } from '../../../../testUtils/mockRequest'; 3 | 4 | export const mockMemberList = [ 5 | { 6 | is_manager: false, 7 | user_name: 'test', 8 | roles: [ 9 | { 10 | instance_name: 'db1', 11 | role_names: ['test1', 'test2', 'test3'], 12 | }, 13 | { 14 | instance_name: 'db2', 15 | role_names: ['test1', 'test2', 'test3'], 16 | }, 17 | { 18 | instance_name: 'db3', 19 | role_names: ['test1', 'test2', 'test3'], 20 | }, 21 | { 22 | instance_name: 'db4', 23 | role_names: ['test1', 'test2', 'test3'], 24 | }, 25 | ], 26 | }, 27 | ]; 28 | 29 | export const mockGetMembers = () => { 30 | const spy = jest.spyOn(user, 'getMembersV1'); 31 | spy.mockImplementation(() => resolveThreeSecond(mockMemberList)); 32 | return spy; 33 | }; 34 | 35 | export const mockDeleteMember = () => { 36 | const spy = jest.spyOn(user, 'deleteMemberV1'); 37 | spy.mockImplementation(() => resolveThreeSecond({})); 38 | return spy; 39 | }; 40 | -------------------------------------------------------------------------------- /src/page/Member/MemberList/index.type.ts: -------------------------------------------------------------------------------- 1 | export type MemberListFilterFormFields = { 2 | filterUserName?: string; 3 | filterInstance?: string; 4 | }; 5 | 6 | export type MemberListFilterFormProps = { 7 | submit: (values: MemberListFilterFormFields) => void; 8 | }; 9 | -------------------------------------------------------------------------------- /src/page/Member/Modal/__test__/utils.ts: -------------------------------------------------------------------------------- 1 | import user from '../../../../api/user'; 2 | import user_group from '../../../../api/user_group'; 3 | import { resolveThreeSecond } from '../../../../testUtils/mockRequest'; 4 | 5 | export const mockAddMember = () => { 6 | const spy = jest.spyOn(user, 'addMemberV1'); 7 | spy.mockImplementation(() => resolveThreeSecond({})); 8 | return spy; 9 | }; 10 | 11 | export const mockUpdateMember = () => { 12 | const spy = jest.spyOn(user, 'updateMemberV1'); 13 | spy.mockImplementation(() => resolveThreeSecond({})); 14 | return spy; 15 | }; 16 | 17 | export const mockAddMemberGroup = () => { 18 | const spy = jest.spyOn(user_group, 'addMemberGroupV1'); 19 | spy.mockImplementation(() => resolveThreeSecond({})); 20 | return spy; 21 | }; 22 | 23 | export const mockUpdateMemberGroup = () => { 24 | const spy = jest.spyOn(user_group, 'updateMemberGroupV1'); 25 | spy.mockImplementation(() => resolveThreeSecond({})); 26 | return spy; 27 | }; 28 | -------------------------------------------------------------------------------- /src/page/Member/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import { ModalName } from '../../../data/ModalName'; 4 | import { initMemberModalStatus } from '../../../store/member'; 5 | import AddMember from './AddMember'; 6 | import AddMemberGroup from './AddMemberGroup'; 7 | import UpdateMember from './UpdateMember'; 8 | import UpdateMemberGroup from './UpdateMemberGroup'; 9 | 10 | const MemberModal: React.FC = () => { 11 | const dispatch = useDispatch(); 12 | useEffect(() => { 13 | dispatch( 14 | initMemberModalStatus({ 15 | modalStatus: { 16 | [ModalName.Add_Member]: false, 17 | [ModalName.Update_Member]: false, 18 | [ModalName.Add_Member_Group]: false, 19 | [ModalName.Update_Member_Group]: false, 20 | }, 21 | }) 22 | ); 23 | }, [dispatch]); 24 | 25 | return ( 26 | <> 27 | 28 | 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default MemberModal; 36 | -------------------------------------------------------------------------------- /src/page/Member/Modal/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IBindRoleReqV1 } from '../../../api/common'; 3 | 4 | export type MemberFormFields = { 5 | isManager: boolean; 6 | roles: IBindRoleReqV1[]; 7 | username: string; 8 | }; 9 | 10 | export type MemberFormProps = { 11 | form: FormInstance; 12 | isUpdate?: boolean; 13 | projectName: string; 14 | isManager?: boolean; 15 | changeIsManager?: (isManager: boolean) => void; 16 | }; 17 | 18 | export type MemberGroupFormFields = { 19 | roles: IBindRoleReqV1[]; 20 | userGroupName: string; 21 | }; 22 | 23 | export type MemberGroupFormProps = { 24 | form: FormInstance; 25 | isUpdate?: boolean; 26 | projectName: string; 27 | }; 28 | -------------------------------------------------------------------------------- /src/page/Member/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test Member should match snapshot 1`] = ` 4 |
5 | 9 |
12 | 13 | 14 |
15 | 16 |
17 | `; 18 | -------------------------------------------------------------------------------- /src/page/Member/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import toJSON from 'enzyme-to-json'; 3 | import Member from '.'; 4 | 5 | describe('test Member', () => { 6 | test('should match snapshot', () => { 7 | const wrapper = shallow(); 8 | expect(toJSON(wrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/Member/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import MemberGroupList from './MemberGroupList'; 4 | import MemberList from './MemberList'; 5 | import MemberModal from './Modal'; 6 | 7 | const Member: React.FC = () => { 8 | const { t } = useTranslation(); 9 | 10 | return ( 11 |
12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 |
20 | ); 21 | }; 22 | export default Member; 23 | -------------------------------------------------------------------------------- /src/page/OperationRecord/OperationRecordList/index.tsx: -------------------------------------------------------------------------------- 1 | import OperationRecordList from './List'; 2 | import { Moment } from 'moment'; 3 | import { FormInstance } from 'antd'; 4 | 5 | export type OperationRecordListFilterFormFields = { 6 | filterDate?: [Moment | undefined, Moment | undefined]; 7 | projectName?: string; 8 | operator?: string; 9 | operationType?: string; 10 | operationAction?: string; 11 | }; 12 | 13 | export type OperationRecordListFilterFormProps = { 14 | updateOperationRecordListFilter: ( 15 | filter: OperationRecordListFilterFormFields 16 | ) => void; 17 | form: FormInstance; 18 | }; 19 | 20 | export default OperationRecordList; 21 | -------------------------------------------------------------------------------- /src/page/OperationRecord/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`OperationRecord should render page header and describe 1`] = ` 4 |
5 | 9 |
12 | 13 |
14 |
15 | `; 16 | -------------------------------------------------------------------------------- /src/page/OperationRecord/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import OperationRecord from '.'; 3 | import toJson from 'enzyme-to-json'; 4 | 5 | describe('OperationRecord', () => { 6 | test('should render page header and describe', () => { 7 | const wrapper = shallow(); 8 | expect(toJson(wrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/OperationRecord/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import OperationRecordList from './OperationRecordList'; 4 | 5 | const OperationRecord: React.FC = () => { 6 | const { t } = useTranslation(); 7 | return ( 8 |
9 | 10 | 11 |
12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default OperationRecord; 19 | -------------------------------------------------------------------------------- /src/page/Order/AuditResult/AuditResultInfo/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../../styles/_variable.less'; 2 | 3 | .result-box { 4 | border-radius: 4px; 5 | padding: 2px 5px; 6 | display: flex; 7 | align-items: center; 8 | .result-box-level { 9 | margin-left: 5px; 10 | width: 110px; 11 | display: inline-box; 12 | } 13 | &-passed { 14 | color: @text-green; 15 | border: 1px solid @text-green; 16 | } 17 | &-warn { 18 | color: @text-orange; 19 | border: 1px solid @text-orange; 20 | } 21 | &-error { 22 | color: @text-red; 23 | border: 1px solid @text-red; 24 | } 25 | &-notice { 26 | color: @text-blue; 27 | border: 1px solid @text-blue; 28 | } 29 | } 30 | 31 | .result-box-list { 32 | display: inline-flex; 33 | gap: 8px; 34 | flex-direction: column; 35 | } 36 | -------------------------------------------------------------------------------- /src/page/Order/AuditResult/AuditResultInfo/index.type.ts: -------------------------------------------------------------------------------- 1 | import { AuditResultErrorMessageProps } from '../../../../components/AuditResultErrorMessage/index.type'; 2 | 3 | export interface AuditResultColumnProps extends AuditResultErrorMessageProps { 4 | auditStatus?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/page/Order/AuditResult/RenderExecuteSql.tsx: -------------------------------------------------------------------------------- 1 | import { Space, Typography } from 'antd'; 2 | import HighlightCode from '../../../utils/HighlightCode'; 3 | import { RenderExecuteSqlProps } from './index.type'; 4 | import CopyIcon from '../../../components/CopyIcon'; 5 | 6 | const RenderExecuteSql: React.FC = ({ 7 | sql, 8 | rows = 10, 9 | }) => { 10 | if (!sql) { 11 | return null; 12 | } 13 | 14 | return ( 15 | 16 | {sql}, 21 | placement: 'topLeft', 22 | overlayInnerStyle: { 23 | width: '30vw', 24 | }, 25 | }, 26 | rows, 27 | }} 28 | className="margin-bottom-0" 29 | > 30 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default RenderExecuteSql; 40 | -------------------------------------------------------------------------------- /src/page/Order/Create/SqlInfoForm/__test__/common.ts: -------------------------------------------------------------------------------- 1 | import instance from '../../../../../api/instance'; 2 | import { resolveThreeSecond } from '../../../../../testUtils/mockRequest'; 3 | 4 | export const mockGetInstance = () => { 5 | const spy = jest.spyOn(instance, 'getInstanceV2'); 6 | spy.mockImplementation(() => 7 | resolveThreeSecond({ 8 | instance_name: 'db1', 9 | db_host: '20.20.20.2', 10 | db_port: '3306', 11 | db_user: 'root', 12 | db_type: 'mysql', 13 | desc: '', 14 | workflow_template_name: 'workflow-template-name-1', 15 | rule_template: { 16 | name: 'not_submit_test_rule33', 17 | is_global_rule_template: true, 18 | }, 19 | }) 20 | ); 21 | 22 | return spy; 23 | }; 24 | -------------------------------------------------------------------------------- /src/page/Order/Create/index.enum.ts: -------------------------------------------------------------------------------- 1 | export enum SQLInputType { 2 | manualInput, 3 | uploadFile, 4 | uploadMybatisFile, 5 | } 6 | -------------------------------------------------------------------------------- /src/page/Order/Detail/Modal/ModifySqlModal/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IAuditTaskResV1 } from '../../../../../api/common.d'; 2 | import { WorkflowResV2ModeEnum } from '../../../../../api/common.enum'; 3 | import { SqlInfoFormFields } from '../../../Create/SqlInfoForm/index.type'; 4 | 5 | export type ModifySqlModalProps = { 6 | cancel: () => void; 7 | submit: ( 8 | tasks: SqlInfoFormFields, 9 | currentTabIndex: number, 10 | currentTabKey: string 11 | ) => Promise; 12 | visible: boolean; 13 | currentOrderTasks?: IAuditTaskResV1[]; 14 | sqlMode: WorkflowResV2ModeEnum; 15 | }; 16 | -------------------------------------------------------------------------------- /src/page/Order/Detail/Modal/OrderHistory/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IWorkflowRecordResV2 } from '../../../../../api/common.d'; 2 | 3 | export type OrderHistoryProps = { 4 | history: IWorkflowRecordResV2[]; 5 | visible: boolean; 6 | close: () => void; 7 | }; 8 | -------------------------------------------------------------------------------- /src/page/Order/List/OrderListFilterForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IGetWorkflowsV1Params } from '../../../../api/workflow/index.d'; 3 | 4 | export type OrderListFilterFormFields = Omit< 5 | IGetWorkflowsV1Params, 6 | | 'page_index' 7 | | 'page_size' 8 | | 'filter_create_time_from' 9 | | 'filter_create_time_to' 10 | | 'filter_task_execute_start_time_from' 11 | | 'filter_task_execute_start_time_to' 12 | | 'project_name' 13 | > & { 14 | filter_order_createTime?: moment.Moment[]; 15 | filter_order_executeTime?: moment.Moment[]; 16 | }; 17 | 18 | export type OrderListFilterFormProps = { 19 | form: FormInstance; 20 | submit: () => void; 21 | reset: () => void; 22 | collapse?: boolean; 23 | collapseChange?: (collapse: boolean) => void; 24 | projectName: string; 25 | }; 26 | -------------------------------------------------------------------------------- /src/page/Order/List/index.data.ts: -------------------------------------------------------------------------------- 1 | export const OrderListUrlParamsKey = { 2 | currentStepAssignee: 'currentStepAssignee', 3 | status: 'status', 4 | createUsername: 'createUsername', 5 | executeTimeForm: 'executeTimeForm', 6 | executeTimeTo: 'executeTimeTo', 7 | }; 8 | -------------------------------------------------------------------------------- /src/page/Order/SqlStatementFormTabs/__test__/test.data.ts: -------------------------------------------------------------------------------- 1 | import { SqlStatementInfoType } from '..'; 2 | 3 | export const sqlStatementInfo: Array = [ 4 | { 5 | key: '1', 6 | instanceName: 'mysql-1', 7 | }, 8 | { 9 | key: '3', 10 | sql: 'select * from user', 11 | instanceName: 'mysql-2', 12 | }, 13 | { 14 | key: '4', 15 | instanceName: 'mysql-3', 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /src/page/ProjectManage/Modal/ProjectForm/ProjectForm.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Input } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { ProjectFormFields, ProjectFormProps } from '.'; 4 | import { ModalFormLayout } from '../../../../data/common'; 5 | import { nameRule } from '../../../../utils/FormRule'; 6 | 7 | const ProjectForm: React.FC = ({ 8 | form, 9 | isUpdate = false, 10 | }) => { 11 | const { t } = useTranslation(); 12 | 13 | return ( 14 | form={form} {...ModalFormLayout}> 15 | 26 | 27 | 28 | 29 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default ProjectForm; 40 | -------------------------------------------------------------------------------- /src/page/ProjectManage/Modal/ProjectForm/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import { useForm } from 'antd/lib/form/Form'; 3 | import ProjectForm from '.'; 4 | import { renderHook } from '@testing-library/react-hooks'; 5 | 6 | describe('test ProjectManage/Modal/ProjectForm', () => { 7 | test('should match snapshot', () => { 8 | const { result } = renderHook(() => useForm()); 9 | const { container, rerender } = render( 10 | 11 | ); 12 | 13 | expect(container).toMatchSnapshot(); 14 | 15 | rerender(); 16 | 17 | expect(container).toMatchSnapshot(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/page/ProjectManage/Modal/ProjectForm/index.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import ProjectForm from './ProjectForm'; 3 | 4 | export type ProjectFormFields = { 5 | projectName: string; 6 | projectDesc: string; 7 | }; 8 | 9 | export type ProjectFormProps = { 10 | form: FormInstance; 11 | isUpdate?: boolean; 12 | }; 13 | 14 | export default ProjectForm; 15 | -------------------------------------------------------------------------------- /src/page/ProjectManage/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import { ModalName } from '../../../data/ModalName'; 4 | import { initProjectManageModalStatus } from '../../../store/projectManage'; 5 | import CreateProject from './CreateProject'; 6 | import UpdateProject from './UpdateProject'; 7 | 8 | const ProjectManageModal: React.FC = () => { 9 | const dispatch = useDispatch(); 10 | 11 | useEffect(() => { 12 | dispatch( 13 | initProjectManageModalStatus({ 14 | modalStatus: { 15 | [ModalName.Create_Project]: false, 16 | [ModalName.Update_Project]: false, 17 | }, 18 | }) 19 | ); 20 | }, [dispatch]); 21 | 22 | return ( 23 | <> 24 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default ProjectManageModal; 31 | -------------------------------------------------------------------------------- /src/page/ProjectManage/ProjectDetail/Layout/__test__/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { SystemRole } from '../../../../../data/common'; 2 | import { projectDetailRouterConfig } from '../../../../../router/config'; 3 | import { generateNavigateMenu } from '../index'; 4 | 5 | describe('test ProjectManage/ProjectDetailLayout/index', () => { 6 | test('should match snapshot', () => { 7 | expect( 8 | generateNavigateMenu(projectDetailRouterConfig, SystemRole.admin, 'test') 9 | ).toMatchSnapshot(); 10 | 11 | expect( 12 | generateNavigateMenu(projectDetailRouterConfig, '', 'test') 13 | ).toMatchSnapshot(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/page/ProjectManage/ProjectDetail/Layout/index.less: -------------------------------------------------------------------------------- 1 | .project-detail-wrapper { 2 | min-height: calc(100vh - 50px); 3 | } 4 | -------------------------------------------------------------------------------- /src/page/ProjectManage/ProjectOverview/Chats/index.tsx: -------------------------------------------------------------------------------- 1 | import { GaugeConfig } from '@ant-design/plots'; 2 | import { PanelCommonProps } from '../Panel'; 3 | 4 | export interface CommonGaugeProps 5 | extends Omit, 6 | Omit { 7 | h: number; 8 | } 9 | -------------------------------------------------------------------------------- /src/page/ProjectManage/ProjectOverview/Panel/PanelWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from 'antd'; 2 | import { PanelWrapperProps } from './index'; 3 | 4 | const PanelWrapper: React.FC = ({ 5 | children, 6 | title, 7 | subTitle, 8 | loading, 9 | error, 10 | bodyStyle, 11 | }) => { 12 | return ( 13 | 25 | {error ? error : children} 26 | 27 | ); 28 | }; 29 | 30 | export default PanelWrapper; 31 | -------------------------------------------------------------------------------- /src/page/ProjectManage/ProjectOverview/Panel/__test__/index.data.ts: -------------------------------------------------------------------------------- 1 | import { PanelCommonProps } from '..'; 2 | 3 | export const panelCommonProps: PanelCommonProps = { 4 | projectName: 'default', 5 | commonPadding: 24, 6 | language: 'zh-CN', 7 | currentTheme: 'light', 8 | }; 9 | -------------------------------------------------------------------------------- /src/page/ProjectManage/ProjectOverview/Panel/index.ts: -------------------------------------------------------------------------------- 1 | import { CardProps } from 'antd'; 2 | import ApprovalProcess from './ApprovalProcess'; 3 | import AuditPlanClassification from './AuditPlanClassification'; 4 | import DataSourceCount from './DataSourceCount'; 5 | import MemberInfo from './MemberInfo'; 6 | import OrderClassification from './OrderClassification'; 7 | import OrderRisk from './OrderRisk'; 8 | import ProjectScore from './ProjectScore'; 9 | import SqlCount from './SqlCount'; 10 | 11 | export type PanelWrapperProps = { 12 | title?: React.ReactNode; 13 | subTitle?: React.ReactNode; 14 | loading: boolean; 15 | error?: React.ReactNode; 16 | children?: React.ReactNode; 17 | bodyStyle?: CardProps['bodyStyle']; 18 | }; 19 | 20 | export type PanelCommonProps = { 21 | projectName: string; 22 | language: string; 23 | currentTheme: string; 24 | commonPadding: number; 25 | }; 26 | 27 | export enum DBHealthEnum { 28 | health = 'health', 29 | risk = 'risk', 30 | } 31 | 32 | export { 33 | ProjectScore, 34 | SqlCount, 35 | DataSourceCount, 36 | ApprovalProcess, 37 | AuditPlanClassification, 38 | MemberInfo, 39 | OrderClassification, 40 | OrderRisk, 41 | }; 42 | -------------------------------------------------------------------------------- /src/page/ProjectManage/ProjectOverview/__test__/ProjectInfoBox.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import ProjectInfoBox from '../ProjectInfoBox'; 3 | 4 | describe('test ProjectInfoBox', () => { 5 | test('should match snapshot', async () => { 6 | const { container, rerender } = render(); 7 | expect(container).toMatchSnapshot(); 8 | 9 | rerender( 10 | 18 | ); 19 | expect(container).toMatchSnapshot(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/page/ProjectManage/ProjectOverview/index.enum.ts: -------------------------------------------------------------------------------- 1 | export enum ProjectOverviewPanelEnum { 2 | ProjectScore = 'ProjectScore', 3 | SqlCount = 'SqlCount', 4 | DataSourceCount = 'DataSourceCount', 5 | OrderClassification = 'OrderClassification', 6 | OrderRisk = 'OrderRisk', 7 | AuditPlanClassification = 'AuditPlanClassification', 8 | AuditPlanRisk = 'AuditPlanRisk', 9 | MemberInfo = 'MemberInfo', 10 | ApprovalProcess = 'ApprovalProcess', 11 | } 12 | -------------------------------------------------------------------------------- /src/page/ProjectManage/ProjectOverview/index.type.ts: -------------------------------------------------------------------------------- 1 | import { ProjectOverviewPanelEnum } from './index.enum'; 2 | 3 | export type ProjectOverviewPanelGridLayout = { 4 | i: ProjectOverviewPanelEnum; 5 | w: number; 6 | h: number; 7 | static: boolean; 8 | }; 9 | -------------------------------------------------------------------------------- /src/page/ReportStatistics/Charts/__test__/__snapshots__/CommonLine.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test CommonLine should match snapshot 1`] = ` 4 |
5 |
16 |
17 | `; 18 | -------------------------------------------------------------------------------- /src/page/ReportStatistics/Charts/__test__/__snapshots__/CommonPie.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test CommonLine should match snapshot 1`] = ` 4 |
5 |
19 |
20 | `; 21 | -------------------------------------------------------------------------------- /src/page/ReportStatistics/Charts/__test__/__snapshots__/CommonRadialBar.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test CommonRadialBar should match snapshot 1`] = ` 4 |
5 |
21 |
22 | `; 23 | -------------------------------------------------------------------------------- /src/page/ReportStatistics/Charts/__test__/index.data.ts: -------------------------------------------------------------------------------- 1 | import i18n from '../../../../locale'; 2 | 3 | const getWeekLocales = () => { 4 | return [ 5 | i18n.t('周一'), 6 | i18n.t('周二'), 7 | i18n.t('周三'), 8 | i18n.t('周四'), 9 | i18n.t('周五'), 10 | i18n.t('周六'), 11 | i18n.t('周日'), 12 | ]; 13 | }; 14 | export const lineData = getWeekLocales().map((w, i) => { 15 | return { 16 | date: w, 17 | value: (i + 1) * 10, 18 | }; 19 | }); 20 | 21 | export const pieData = [ 22 | { 23 | instance_type: 'Mysql', 24 | percent: 27, 25 | }, 26 | { 27 | instance_type: 'Oracle', 28 | percent: 25, 29 | }, 30 | { 31 | instance_type: 'Redis', 32 | percent: 18, 33 | }, 34 | { 35 | instance_type: 'MongoDB', 36 | percent: 15, 37 | }, 38 | { 39 | instance_type: 'TiDB', 40 | percent: 5, 41 | }, 42 | ]; 43 | 44 | export const radialBarData = [ 45 | { 46 | type: 'test1', 47 | percent: 0.1, 48 | }, 49 | { 50 | type: 'test2', 51 | percent: 0.4, 52 | }, 53 | { 54 | type: 'test3', 55 | percent: 1.1, 56 | }, 57 | ]; 58 | -------------------------------------------------------------------------------- /src/page/ReportStatistics/Charts/index.ts: -------------------------------------------------------------------------------- 1 | import { LineConfig, PieConfig, RadialBarConfig } from '@ant-design/plots'; 2 | 3 | export interface CommonLineProps 4 | extends Omit { 5 | h: number; 6 | } 7 | 8 | export interface CommonPieProps 9 | extends Omit { 10 | h: number; 11 | } 12 | 13 | export interface CommonRadialBarProps 14 | extends Omit { 15 | h: number; 16 | } 17 | 18 | export const CommonChartsColors = [ 19 | '#1890ff', 20 | '#ffa940', 21 | '#eb2f96', 22 | '#ff9c6e', 23 | '#fadb14', 24 | '#bae637', 25 | '#ffd666', 26 | '#52c41a', 27 | '#5cdbd3', 28 | ]; 29 | -------------------------------------------------------------------------------- /src/page/ReportStatistics/Panel/PanelWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from 'antd'; 2 | import { PanelWrapperProps } from './index'; 3 | 4 | const PanelWrapper: React.FC = ({ 5 | children, 6 | title, 7 | subTitle, 8 | loading, 9 | error, 10 | }) => { 11 | return ( 12 | 23 | {error ? error : children} 24 | 25 | ); 26 | }; 27 | 28 | export default PanelWrapper; 29 | -------------------------------------------------------------------------------- /src/page/ReportStatistics/index.enum.ts: -------------------------------------------------------------------------------- 1 | export enum ReportStatisticsPanelEnum { 2 | OrderTotalNumbers = 'OrderTotalNumbers', 3 | OrderAverageReviewTime = 'OrderAverageReviewTime', 4 | OrderPassPercent = 'OrderPassPercent', 5 | OrderQuantityTrend = 'OrderQuantityTrend', 6 | OrderStatus = 'OrderStatus', 7 | OrderQuantityWithDbType = 'OrderQuantityWithDbType', 8 | InstanceProportionWithDbType = 'InstanceProportionWithDbType', 9 | LicenseUsage = 'LicenseUsage', 10 | DiffUserOrderRejectedPercent = 'DiffUserOrderRejectedPercent', 11 | orderAverageExecuteTimeTopN = 'orderAverageExecuteTimeTopN', 12 | sqlExecFailedTopN = 'sqlExecFailedTopN', 13 | } 14 | -------------------------------------------------------------------------------- /src/page/ReportStatistics/index.less: -------------------------------------------------------------------------------- 1 | @import '../../styles//_variable.less'; 2 | .page-report-statistics-namespace { 3 | .statistics-value-style { 4 | span { 5 | font-size: 20px; 6 | font-weight: 600; 7 | color: @text-blue; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/page/ReportStatistics/index.type.ts: -------------------------------------------------------------------------------- 1 | import { ReportStatisticsPanelEnum } from './index.enum'; 2 | 3 | export interface IPanelGridLayout { 4 | i: ReportStatisticsPanelEnum; 5 | w: number; 6 | h: number; 7 | static: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /src/page/Rule/__test__/__snapshots__/useRuleFilterForm.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test useRuleFilterForm should be match snapshot of rule template options 1`] = ` 4 | 7 | 12 | 17 | 18 | `; 19 | 20 | exports[`test useRuleFilterForm should be match snapshot of rule template options 2`] = ` 21 | 24 | 29 | 34 | 35 | `; 36 | -------------------------------------------------------------------------------- /src/page/RuleKnowledge/RuleUnderstand/EditKnowledgeContent.tsx: -------------------------------------------------------------------------------- 1 | import MDEditor from '@uiw/react-md-editor'; 2 | import { EditKnowledgeContentProps } from './index.type'; 3 | import rehypeSanitize from 'rehype-sanitize'; 4 | 5 | const EditKnowledgeContent: React.FC = ({ 6 | value, 7 | onChange, 8 | setHasDirtyData, 9 | }) => { 10 | return ( 11 | { 18 | onChange?.(v); 19 | setHasDirtyData(true); 20 | }} 21 | /> 22 | ); 23 | }; 24 | 25 | export default EditKnowledgeContent; 26 | -------------------------------------------------------------------------------- /src/page/RuleKnowledge/RuleUnderstand/index.type.ts: -------------------------------------------------------------------------------- 1 | import { MDEditorProps } from '@uiw/react-md-editor'; 2 | 3 | export type RuleUnderstandProps = { 4 | ruleName: string; 5 | content?: string; 6 | refresh: () => void; 7 | dbType: string; 8 | loading: boolean; 9 | isAdmin: boolean; 10 | isCustomRule: boolean; 11 | }; 12 | 13 | export type EditKnowledgeContentProps = { 14 | value: MDEditorProps['value']; 15 | onChange: MDEditorProps['onChange']; 16 | setHasDirtyData: (val: boolean) => void; 17 | }; 18 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/ImportRuleTemplate/index.tsx: -------------------------------------------------------------------------------- 1 | import ImportRuleTemplate from './ImportRuleTemplate'; 2 | 3 | export type SelectFileFormFields = { 4 | ruleTemplateFile: any; 5 | }; 6 | 7 | export default ImportRuleTemplate; 8 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/RuleTemplateForm/BaseInfoForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IRuleProjectTemplateDetailResV1 } from '../../../../api/common'; 3 | import { RuleTemplateFormProps } from '../index.type'; 4 | 5 | export type RuleTemplateBaseInfoFields = { 6 | templateName: string; 7 | templateDesc?: string; 8 | db_type: string; 9 | }; 10 | 11 | export type RuleTemplateBaseInfoFormProps = { 12 | form: FormInstance; 13 | defaultData?: IRuleProjectTemplateDetailResV1; 14 | submit: () => void; 15 | projectName: string; 16 | mode: RuleTemplateFormProps['mode']; 17 | }; 18 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/RuleTemplateForm/RuleSelect/RuleManagerModal/index.less: -------------------------------------------------------------------------------- 1 | .rule-manager-modal { 2 | .ant-input-disabled { 3 | background: transparent; 4 | border: none; 5 | box-shadow: none; 6 | padding-left: 0; 7 | cursor: default; 8 | } 9 | textarea { 10 | resize: none; 11 | } 12 | .wrap-form-item { 13 | .ant-form-item-label { 14 | white-space: pre-wrap; 15 | line-height: 14px; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/RuleTemplateForm/RuleSelect/RuleManagerModal/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IRuleResV1 } from '../../../../../api/common'; 2 | import { RuleResV1LevelEnum } from '../../../../../api/common.enum'; 3 | 4 | export type RuleManagerFormProps = { 5 | visible: boolean; 6 | ruleData?: IRuleResV1 | undefined; 7 | setVisibleFalse: () => void; 8 | submit: (values: IRuleResV1) => void; 9 | }; 10 | 11 | export interface IRuleManagerForm { 12 | rule_name: string; 13 | desc: string; 14 | annotation: string; 15 | type: string; 16 | db_type: string; 17 | level?: RuleResV1LevelEnum; 18 | params: Record; 19 | } 20 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/RuleTemplateForm/RuleSelect/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IRuleResV1 } from '../../../../api/common'; 2 | 3 | export type RuleSelectProps = { 4 | listLoading: boolean; 5 | allRules: IRuleResV1[]; 6 | activeRule: IRuleResV1[]; 7 | updateActiveRule: (value: IRuleResV1[]) => void; 8 | }; 9 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/RuleTemplateForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { 3 | IRuleProjectTemplateDetailResV1, 4 | IRuleResV1, 5 | } from '../../../api/common'; 6 | import { RuleTemplateBaseInfoFields } from './BaseInfoForm/index.type'; 7 | 8 | export type RuleTemplateFormProps = { 9 | form: FormInstance; 10 | defaultData?: IRuleProjectTemplateDetailResV1; 11 | activeRule: IRuleResV1[]; 12 | allRules: IRuleResV1[]; 13 | ruleListLoading: boolean; 14 | submitLoading: boolean; 15 | step: number; 16 | updateActiveRule: (value: IRuleResV1[]) => void; 17 | baseInfoSubmit: () => void; 18 | prevStep: () => void; 19 | submit: () => void; 20 | projectName: string; 21 | mode: 'import' | 'update' | 'create'; 22 | children: React.ReactNode; 23 | }; 24 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/RuleTemplateList/Modal/CloneRuleTemplate/index.type.ts: -------------------------------------------------------------------------------- 1 | export type CloneRuleTemplateFormFields = { 2 | templateName: string; 3 | templateDesc?: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/RuleTemplateList/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import CloneRuleTemplateModal from './CloneRuleTemplate'; 2 | 3 | const RuleTemplateListModal = () => { 4 | return ; 5 | }; 6 | 7 | export default RuleTemplateListModal; 8 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`RuleTemplate should render page header and describe 1`] = ` 4 |
7 | 11 | ruleTemplate.pageDescribe 12 | 13 |
16 | 17 |
18 |
19 | `; 20 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import RuleTemplate from '.'; 3 | import toJson from 'enzyme-to-json'; 4 | 5 | describe('RuleTemplate', () => { 6 | test('should render page header and describe', () => { 7 | const wrapper = shallow(); 8 | expect(toJson(wrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/RuleTemplate/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { Outlet } from 'react-router-dom'; 4 | 5 | const RuleTemplate = () => { 6 | const { t } = useTranslation(); 7 | 8 | return ( 9 |
10 | 11 | {t('ruleTemplate.pageDescribe')} 12 | 13 |
14 | 15 |
16 |
17 | ); 18 | }; 19 | 20 | export default RuleTemplate; 21 | -------------------------------------------------------------------------------- /src/page/SQLManagement/SQLPanel/index.less: -------------------------------------------------------------------------------- 1 | .sql-panel-statistics-wrapper { 2 | .ant-card-body { 3 | padding: 12px 20px; 4 | } 5 | } 6 | .sql-management-table-namespace { 7 | .table-column-sql-fingerprint, 8 | .table-column-sql { 9 | max-width: 420px; 10 | } 11 | .table-column-source { 12 | max-width: 200px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/page/SQLManagement/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import EnterpriseFeatureDisplay from '../../components/EnterpriseFeatureDisplay/EnterpriseFeatureDisplay'; 4 | import SQLPanel from './SQLPanel'; 5 | 6 | const SQLManagement: React.FC = () => { 7 | const { t } = useTranslation(); 8 | return ( 9 | <> 10 | 11 | {t('sqlManagement.pageDesc')} 12 | 13 | 14 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | 24 | export default SQLManagement; 25 | -------------------------------------------------------------------------------- /src/page/SqlAnalyze/AuditPlan/index.type.ts: -------------------------------------------------------------------------------- 1 | export type AuditPlanReportSqlAnalyzeUrlParams = { 2 | reportId: string; 3 | sqlNum: string; 4 | auditPlanName: string; 5 | }; 6 | -------------------------------------------------------------------------------- /src/page/SqlAnalyze/Order/index.type.ts: -------------------------------------------------------------------------------- 1 | export type OrderSqlAnalyzeUrlParams = { 2 | taskId: string; 3 | sqlNum: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/page/SqlAnalyze/SqlAnalyze/index.tsx: -------------------------------------------------------------------------------- 1 | import { ResultStatusType } from 'antd/lib/result'; 2 | import { 3 | IPerformanceStatistics, 4 | ISQLExplain, 5 | ITableMeta, 6 | ITableMetas, 7 | } from '../../../api/common'; 8 | import SqlAnalyze from './SqlAnalyze'; 9 | 10 | export type SqlAnalyzeProps = { 11 | errorMessage: string; 12 | errorType?: ResultStatusType; 13 | tableMetas?: ITableMetas; 14 | sqlExplain?: ISQLExplain; 15 | performanceStatistics?: IPerformanceStatistics; 16 | loading?: boolean; 17 | }; 18 | 19 | export type UseTableSchemaOption = { 20 | schemaName?: string; 21 | dataSourceName?: string; 22 | }; 23 | 24 | export type TableSchemaItem = { 25 | tableMeta: ITableMeta; 26 | id: string; 27 | errorMessage: string; 28 | }; 29 | 30 | export default SqlAnalyze; 31 | -------------------------------------------------------------------------------- /src/page/SqlAnalyze/SqlManage/index.type.ts: -------------------------------------------------------------------------------- 1 | export type SQLManageAnalyzeUrlParams = { 2 | sqlManageId: string; 3 | }; 4 | -------------------------------------------------------------------------------- /src/page/SqlAnalyze/__testData__/index.ts: -------------------------------------------------------------------------------- 1 | import { sqlExecPlans, tableSchemas } from '../../SqlQuery/__testData__'; 2 | 3 | export const AuditPlanSqlAnalyzeData = { 4 | sql_explain: { 5 | sql: sqlExecPlans[0].sql, 6 | classic_result: sqlExecPlans[0].classic_result, 7 | performance_statistics: { 8 | affect_rows: { 9 | count: 10, 10 | err_message: '', 11 | }, 12 | }, 13 | }, 14 | table_metas: { 15 | err_message: '', 16 | table_meta_items: tableSchemas.map((e) => e.tableMeta), 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/page/SqlAuditRecord/Create/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | 3 | export type BaseInfoFormFields = { 4 | tags: string[]; 5 | }; 6 | 7 | export type BaseInfoFormProps = { 8 | form: FormInstance; 9 | projectName: string; 10 | }; 11 | 12 | export type BaseInfoFormRef = { 13 | reset: () => void; 14 | }; 15 | 16 | export type SQLInfoFormRef = { 17 | reset: () => void; 18 | }; 19 | 20 | export type SQLInfoFormFields = { 21 | auditType: AuditTypeEnum; 22 | uploadType: UploadTypeEnum; 23 | sql: string; 24 | sqlFile: File[]; 25 | mybatisFile: File[]; 26 | zipFile: File[]; 27 | instanceName: string; 28 | instanceSchema: string; 29 | dbType: string; 30 | gitHttpUrl: string; 31 | gitUserName: string; 32 | gitUserPassword: string; 33 | }; 34 | 35 | export type SQLInfoFormProps = { 36 | form: FormInstance; 37 | submit: (values: SQLInfoFormFields) => Promise; 38 | projectName: string; 39 | }; 40 | 41 | export enum AuditTypeEnum { 42 | static, 43 | dynamic, 44 | } 45 | 46 | export enum UploadTypeEnum { 47 | sql, 48 | sqlFile, 49 | xmlFile, 50 | zipFile, 51 | git, 52 | } 53 | -------------------------------------------------------------------------------- /src/page/SqlAuditRecord/List/index.data.ts: -------------------------------------------------------------------------------- 1 | export const SQLAuditRecordListUrlParamsKey = { 2 | SQLAuditRecordID: 'SQLAuditRecordID', 3 | }; 4 | 5 | export const SQLAuditRecordIDValuesSplit = ','; 6 | -------------------------------------------------------------------------------- /src/page/SqlAuditRecord/List/index.less: -------------------------------------------------------------------------------- 1 | .custom-tags-wrapper { 2 | .ant-popover-inner-content { 3 | padding: 4px; 4 | 5 | .custom-tag-item { 6 | cursor: pointer; 7 | padding: 8px; 8 | border-radius: 4px; 9 | } 10 | 11 | .ant-divider-horizontal { 12 | margin: 0 0 8px 0; 13 | } 14 | 15 | .add-tag-content { 16 | padding: 0 8px 4px; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/page/SqlAuditRecord/List/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IGetSQLAuditRecordsV1Params } from '../../../api/sql_audit_record/index.d'; 3 | 4 | export type SQLAuditListFilterFormFields = Omit< 5 | IGetSQLAuditRecordsV1Params, 6 | | 'page_index' 7 | | 'page_size' 8 | | 'project_name' 9 | | 'filter_create_time_to' 10 | | 'filter_create_time_from' 11 | > & { 12 | filter_create_time?: moment.Moment[]; 13 | }; 14 | 15 | export type SQLAuditListFilterFormProps = { 16 | form: FormInstance; 17 | submit: () => void; 18 | projectName: string; 19 | reset: () => void; 20 | }; 21 | export type CustomTagsProps = { 22 | updateTags: (tags: string[]) => Promise; 23 | tags: string[]; 24 | projectName: string; 25 | }; 26 | -------------------------------------------------------------------------------- /src/page/SqlQuery/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { ISQLExplain } from '../../api/common'; 3 | 4 | export type SqlQueryResultType = { 5 | sqlQueryId: string; 6 | hide: boolean; 7 | errorMessage: string; 8 | }; 9 | 10 | export interface ISqlInputForm { 11 | instanceName: string; 12 | instanceSchema: string; 13 | sql: string; 14 | maxPreQueryRows: number; 15 | } 16 | 17 | export type UseSQLExecPlanOption = { 18 | form: FormInstance; 19 | }; 20 | 21 | export type SQLExecPlanItem = { 22 | id: string; 23 | hide: boolean; 24 | } & ISQLExplain; 25 | -------------------------------------------------------------------------------- /src/page/SyncDataSource/SyncTaskForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IInstanceTaskDetailResV1 } from '../../../api/common'; 3 | import SyncTaskForm from './SyncTaskForm'; 4 | 5 | export type SyncTaskFormFields = { 6 | source: string; 7 | version: string; 8 | url: string; 9 | instanceType: string; 10 | ruleTemplateName: string; 11 | syncInterval: string; 12 | }; 13 | 14 | export type SyncTaskFormProps = { 15 | submit: (values: SyncTaskFormFields) => Promise; 16 | form: FormInstance; 17 | defaultValue?: IInstanceTaskDetailResV1; 18 | }; 19 | 20 | export default SyncTaskForm; 21 | -------------------------------------------------------------------------------- /src/page/SyncDataSource/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test SyncDataSource should match snapshot 1`] = ` 4 | 5 |
6 | 10 | syncDataSource.pageDesc 11 | 12 | 16 |
19 | 20 |
21 |
22 |
23 |
24 | `; 25 | -------------------------------------------------------------------------------- /src/page/SyncDataSource/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import toJson from 'enzyme-to-json'; 3 | import SyncDataSource from '.'; 4 | 5 | describe('test SyncDataSource', () => { 6 | test('should match snapshot', () => { 7 | const wrapper = shallow(); 8 | expect(toJson(wrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/SyncDataSource/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { Outlet } from 'react-router-dom'; 4 | import EnterpriseFeatureDisplay from '../../components/EnterpriseFeatureDisplay/EnterpriseFeatureDisplay'; 5 | 6 | const SyncDataSource: React.FC = () => { 7 | const { t } = useTranslation(); 8 | 9 | return ( 10 | <> 11 |
12 | 13 | {t('syncDataSource.pageDesc')} 14 | 15 | 16 | 20 |
21 | 22 |
23 |
24 |
25 | 26 | ); 27 | }; 28 | 29 | export default SyncDataSource; 30 | -------------------------------------------------------------------------------- /src/page/System/LDAPSetting/index.type.ts: -------------------------------------------------------------------------------- 1 | export type LDAPFormFields = { 2 | enable_ldap: boolean; 3 | enable_ssl: boolean; 4 | ldap_server_host?: string; 5 | ldap_server_port?: string; 6 | ldap_connect_dn?: string; 7 | ldap_connect_pwd?: string; 8 | ldap_search_base_dn?: string; 9 | ldap_user_name_rdn_key?: string; 10 | ldap_user_email_rdn_key?: string; 11 | }; 12 | -------------------------------------------------------------------------------- /src/page/System/LarkSetting/index.tsx: -------------------------------------------------------------------------------- 1 | import { TestFeishuConfigurationReqV1AccountTypeEnum } from '../../../api/common.enum'; 2 | import LarkSetting from './LarkSetting'; 3 | 4 | export default LarkSetting; 5 | 6 | export type FormFields = { 7 | enabled: boolean; 8 | appKey: string; 9 | appSecret: string; 10 | }; 11 | 12 | export type TestFormFields = { 13 | receiveType: TestFeishuConfigurationReqV1AccountTypeEnum; 14 | receivePhone: string; 15 | receiveEmail: string; 16 | }; 17 | -------------------------------------------------------------------------------- /src/page/System/License/__testData__/index.ts: -------------------------------------------------------------------------------- 1 | export const licenseList = [ 2 | { 3 | description: '服务器', 4 | limit: '10', 5 | name: 'server', 6 | }, 7 | { 8 | description: '服务器2', 9 | limit: '9', 10 | name: 'server2', 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /src/page/System/Oauth/__testData__/index.ts: -------------------------------------------------------------------------------- 1 | export const oauthConfig = { 2 | access_token_tag: 'rpYV2tN4&545Jvkd3%J6', 3 | client_host: 'news://egyolphgg.tm/lundgwlpz', 4 | client_id: '6lq#s#aRibpMvhp48ztHOg@sZ3PxA2e(MYdS!CJANzLPBdg]m)', 5 | enable_oauth2: false, 6 | login_tip: 'VT[9[I$M(EW5R9o12*&Z', 7 | scopes: ['1XGCBu%brJrwjse@R^Ox', 'lpSLVoFnqZBfGHeI8023'], 8 | server_auth_url: 'prospero://tpmui.cf/timilp', 9 | server_token_url: 'mid://juckyny.na/xxsxnmf', 10 | server_user_id_url: 'cid://hqpbmxvbpl.cd/lcfyjtlkuj', 11 | user_id_tag: 'NFkVxY[4Xv^UFU&x&t5y', 12 | }; 13 | -------------------------------------------------------------------------------- /src/page/System/Oauth/index.type.ts: -------------------------------------------------------------------------------- 1 | export type OauthFormField = { 2 | enable: boolean; 3 | clientId: string; 4 | clientSecret: string; 5 | clientHost: string; 6 | serverAuthUrl: string; 7 | serverTokenUrl: string; 8 | serverUserIdUrl: string; 9 | scopes: string; 10 | accessTokenKeyName: string; 11 | userIdKeyName: string; 12 | loginButtonText: string; 13 | }; 14 | -------------------------------------------------------------------------------- /src/page/UserCenter/Common/__snapshots__/index.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test generateTag should match snapshot for generateTag 1`] = ` 4 | Array [ 5 | 6 | group1 7 | , 8 | 9 | group2 10 | , 11 | ] 12 | `; 13 | -------------------------------------------------------------------------------- /src/page/UserCenter/Common/generateTag.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from 'antd'; 2 | 3 | export default function generateTag(list: string[]) { 4 | return list?.map((r) => {r}); 5 | } 6 | -------------------------------------------------------------------------------- /src/page/UserCenter/Common/index.test.ts: -------------------------------------------------------------------------------- 1 | import generateTag from './generateTag'; 2 | 3 | describe('test generateTag', () => { 4 | test('should match snapshot for generateTag', () => { 5 | const list = ['group1', 'group2']; 6 | expect(generateTag(list)).toMatchSnapshot(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/page/UserCenter/Role/Modal/RoleForm/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import { renderHook } from '@testing-library/react-hooks'; 3 | import { useForm } from 'antd/lib/form/Form'; 4 | import RoleForm from '.'; 5 | 6 | describe('User/Modal/RoleForm', () => { 7 | test('should match snapshot', () => { 8 | const { result } = renderHook(() => useForm()); 9 | const { container } = render( 10 | 11 | ); 12 | expect(container).toMatchSnapshot(); 13 | }); 14 | 15 | test('should match snapshot when isUpdate is truthy', () => { 16 | const { result } = renderHook(() => useForm()); 17 | const { container } = render( 18 | 19 | ); 20 | expect(container).toMatchSnapshot(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/page/UserCenter/Role/Modal/RoleForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IOperationResV1 } from '../../../../../api/common.d'; 3 | 4 | export interface IRoleFormFields { 5 | roleName: string; 6 | roleDesc?: string; 7 | operationCodes?: number[]; 8 | isDisabled?: boolean; 9 | } 10 | 11 | export interface IRoleFormProps { 12 | form: FormInstance; 13 | operationList: IOperationResV1[]; 14 | isUpdate?: boolean; 15 | } 16 | -------------------------------------------------------------------------------- /src/page/UserCenter/Role/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import AddRole from './AddRole'; 2 | import UpdateRole from './UpdateRole'; 3 | 4 | const RoleModal = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default RoleModal; 14 | -------------------------------------------------------------------------------- /src/page/UserCenter/Role/RoleList/index.type.ts: -------------------------------------------------------------------------------- 1 | export type RoleListFilter = { 2 | filter_role_name?: string; 3 | }; 4 | -------------------------------------------------------------------------------- /src/page/UserCenter/User/Modal/UserForm/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import { renderHook } from '@testing-library/react-hooks'; 3 | import { useForm } from 'antd/lib/form/Form'; 4 | import UserForm from '.'; 5 | 6 | describe('User/Modal/UserForm', () => { 7 | test('should match snapshot when isUpdate is falsy', () => { 8 | const { result } = renderHook(() => useForm()); 9 | const { container } = render( 10 | 15 | ); 16 | expect(container).toMatchSnapshot(); 17 | }); 18 | 19 | test('should match snapshot when isUpdate is truthy', () => { 20 | const { result } = renderHook(() => useForm()); 21 | const { container } = render( 22 | 28 | ); 29 | expect(container).toMatchSnapshot(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/page/UserCenter/User/Modal/UserForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { 3 | IManagementPermissionResV1, 4 | IUserGroupTipListItem, 5 | } from '../../../../../api/common.d'; 6 | 7 | export interface IUserFormFields { 8 | username: string; 9 | password: string; 10 | passwordAgain: string; 11 | email?: string; 12 | disabled: boolean; 13 | userGroupList?: string[]; 14 | wechat?: string; 15 | managementPermissionCodeList?: number[]; 16 | phone?: string; 17 | } 18 | 19 | export interface IUserFormProps { 20 | form: FormInstance; 21 | userGroupList: IUserGroupTipListItem[]; 22 | isUpdate?: boolean; 23 | isAdmin?: boolean; 24 | managementPermissionList: IManagementPermissionResV1[]; 25 | } 26 | -------------------------------------------------------------------------------- /src/page/UserCenter/User/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import AddUser from './AddUser'; 2 | import ModifyUserPassword from './ModifyUserPassword'; 3 | import UpdateUser from './UpdateUser'; 4 | 5 | const UserModal = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default UserModal; 16 | -------------------------------------------------------------------------------- /src/page/UserCenter/User/UserList/index.type.ts: -------------------------------------------------------------------------------- 1 | export type UserListFilter = { 2 | filter_user_name?: string; 3 | }; 4 | -------------------------------------------------------------------------------- /src/page/UserCenter/UserGroup/Modal/UserGroupForm/UserGroupForm.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import UserGroupForm from '.'; 3 | 4 | describe('UserGroupForm', () => { 5 | it('should match snapshot', () => { 6 | const { container } = render( 7 | 14 | ); 15 | expect(container).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/page/UserCenter/UserGroup/Modal/UserGroupForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { IUserTipResV1 } from '../../../../../api/common'; 3 | import UserGroupForm from './UserGroupForm'; 4 | 5 | export type UserGroupFormProps = { 6 | form?: FormInstance; 7 | userList?: IUserTipResV1[]; 8 | isUpdate?: boolean; 9 | }; 10 | export type UserGroupFormField = { 11 | userGroupName: string; 12 | userGroupDesc?: string; 13 | userList?: string[]; 14 | isDisabled?: boolean; 15 | }; 16 | 17 | export default UserGroupForm; 18 | -------------------------------------------------------------------------------- /src/page/UserCenter/UserGroup/Modal/UserGroupForm/useUserGroupFormOption.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import useUsername from '../../../../../hooks/useUsername'; 3 | 4 | const useUserGroupFormOption = (visible: boolean) => { 5 | const { usernameList, updateUsernameList } = useUsername(); 6 | 7 | useEffect(() => { 8 | if (visible) { 9 | updateUsernameList(); 10 | } 11 | }, [updateUsernameList, visible]); 12 | 13 | return { 14 | usernameList, 15 | }; 16 | }; 17 | 18 | export default useUserGroupFormOption; 19 | -------------------------------------------------------------------------------- /src/page/UserCenter/UserGroup/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import AddUserGroup from './AddUserGroup'; 2 | import UpdateUserGroup from './UpdateUserGroup'; 3 | 4 | const UserGroupModal = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default UserGroupModal; 14 | -------------------------------------------------------------------------------- /src/page/UserCenter/UserGroup/UserGroupList/TableFilterForm/index.tsx: -------------------------------------------------------------------------------- 1 | import TableFilterForm from './TableFilterForm'; 2 | 3 | export type TableFilterFormProps = { 4 | updateTableFilter: (values: UserGroupListFilter) => void; 5 | }; 6 | 7 | export type UserGroupListFilter = { 8 | filter_user_group_name?: string; 9 | }; 10 | 11 | export default TableFilterForm; 12 | -------------------------------------------------------------------------------- /src/page/UserCenter/UserManageModal.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useDispatch } from 'react-redux'; 3 | import { ModalName } from '../../data/ModalName'; 4 | import { initUserManageModalStatus } from '../../store/userManage'; 5 | import RoleModal from './Role/Modal'; 6 | import UserModal from './User/Modal'; 7 | import UserGroupModal from './UserGroup/Modal'; 8 | 9 | const UserManageModal: React.FC = () => { 10 | const dispatch = useDispatch(); 11 | 12 | useEffect(() => { 13 | dispatch( 14 | initUserManageModalStatus({ 15 | modalStatus: { 16 | [ModalName.Add_User]: false, 17 | [ModalName.Update_User]: false, 18 | [ModalName.Add_Role]: false, 19 | [ModalName.Update_Role]: false, 20 | [ModalName.Add_User_Group]: false, 21 | [ModalName.Update_User_Group]: false, 22 | [ModalName.Update_User_Password]: false, 23 | }, 24 | }) 25 | ); 26 | }, [dispatch]); 27 | 28 | return ( 29 | <> 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default UserManageModal; 38 | -------------------------------------------------------------------------------- /src/page/UserCenter/index.tsx: -------------------------------------------------------------------------------- 1 | import { GlobalRouterItemKeyLiteral } from '../../types/router.type'; 2 | import UserCenter from './UserCenter'; 3 | 4 | export type UserCenterActiveTabKey = Extract< 5 | GlobalRouterItemKeyLiteral, 6 | 'user' | 'role' | 'userGroup' 7 | >; 8 | 9 | export default UserCenter; 10 | -------------------------------------------------------------------------------- /src/page/Whitelist/WhitelistForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { FormInstance } from 'antd'; 2 | import { CreateAuditWhitelistReqV1MatchTypeEnum } from '../../../api/common.enum'; 3 | 4 | export type WhitelistFormFields = { 5 | desc?: string; 6 | sql: string; 7 | matchType: CreateAuditWhitelistReqV1MatchTypeEnum; 8 | }; 9 | 10 | export type WhitelistFormProps = { 11 | form: FormInstance; 12 | }; 13 | -------------------------------------------------------------------------------- /src/page/Whitelist/WhitelistList/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import AddWhitelist from './AddWhitelist'; 2 | import UpdateWhitelist from './UpdateWhitelist'; 3 | 4 | const WhitelistModal = () => { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default WhitelistModal; 14 | -------------------------------------------------------------------------------- /src/page/Whitelist/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Whitelist should match snapshot 1`] = ``; 4 | -------------------------------------------------------------------------------- /src/page/Whitelist/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import toJson from 'enzyme-to-json'; 3 | import Whitelist from '.'; 4 | 5 | describe('Whitelist', () => { 6 | test('should match snapshot', () => { 7 | const wrapper = shallow(); 8 | expect(toJson(wrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/Whitelist/index.tsx: -------------------------------------------------------------------------------- 1 | import WhitelistList from './WhitelistList'; 2 | 3 | const Whitelist = () => { 4 | return ; 5 | }; 6 | 7 | export default Whitelist; 8 | -------------------------------------------------------------------------------- /src/page/WorkflowTemplate/WorkflowTemplateForm/BaseForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { IWorkflowTemplateDetailResV1 } from '../../../../api/common'; 2 | import { WorkflowTemplateDetailResV1AllowSubmitWhenLessAuditLevelEnum } from '../../../../api/common.enum'; 3 | 4 | export type BaseFormProps = { 5 | defaultData?: IWorkflowTemplateDetailResV1; 6 | nextStep: () => void; 7 | updateBaseInfo: (info: BaseFormFields) => void; 8 | projectName: string; 9 | }; 10 | 11 | export type BaseFormFields = { 12 | allowSubmitWhenLessAuditLevel?: WorkflowTemplateDetailResV1AllowSubmitWhenLessAuditLevelEnum; 13 | }; 14 | -------------------------------------------------------------------------------- /src/page/WorkflowTemplate/WorkflowTemplateForm/ProgressConfig/index.type.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IWorkFlowStepTemplateReqV1, 3 | IWorkflowTemplateDetailResV1, 4 | } from '../../../../api/common'; 5 | 6 | export type ProgressConfigProps = { 7 | defaultData?: IWorkflowTemplateDetailResV1; 8 | submitLoading: boolean; 9 | prevStep: () => void; 10 | submitProgressConfig: (configs: IWorkFlowStepTemplateReqV1[]) => void; 11 | projectName: string; 12 | }; 13 | 14 | export type ProgressConfigItem = Required< 15 | Omit 16 | >; 17 | 18 | export type ExecProgressConfigItem = Required< 19 | Omit 20 | >; 21 | -------------------------------------------------------------------------------- /src/page/WorkflowTemplate/WorkflowTemplateForm/index.type.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | import { 3 | IWorkFlowStepTemplateReqV1, 4 | IWorkflowTemplateDetailResV1, 5 | } from '../../../api/common'; 6 | import { IUpdateWorkflowTemplateV1Return } from '../../../api/workflow/index.d'; 7 | import { BaseFormFields } from './BaseForm/index.type'; 8 | 9 | export type WorkflowTemplateFormProps = { 10 | projectName: string; 11 | defaultData?: IWorkflowTemplateDetailResV1; 12 | updateBaseInfo: (info: BaseFormFields) => void; 13 | submitProgress: ( 14 | process: IWorkFlowStepTemplateReqV1[] 15 | ) => Promise>; 16 | children?: React.ReactNode; 17 | }; 18 | -------------------------------------------------------------------------------- /src/page/WorkflowTemplate/__snapshots__/index.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`WorkflowTemplate should match snapshot 1`] = ` 4 |
5 | 9 | workflowTemplate.pageDesc 10 | 11 |
14 | 15 |
16 |
17 | `; 18 | -------------------------------------------------------------------------------- /src/page/WorkflowTemplate/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { shallow } from 'enzyme'; 2 | import toJson from 'enzyme-to-json'; 3 | import WorkflowTemplate from '.'; 4 | 5 | describe('WorkflowTemplate', () => { 6 | test('should match snapshot', () => { 7 | const shallowWrapper = shallow(); 8 | expect(toJson(shallowWrapper)).toMatchSnapshot(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/page/WorkflowTemplate/index.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from 'antd'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { Outlet } from 'react-router-dom'; 4 | 5 | const WorkflowTemplate = () => { 6 | const { t } = useTranslation(); 7 | return ( 8 |
9 | 10 | {t('workflowTemplate.pageDesc')} 11 | 12 |
13 | 14 |
15 |
16 | ); 17 | }; 18 | 19 | export default WorkflowTemplate; 20 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/router/RouterAuth.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux'; 2 | import { Navigate, useLocation } from 'react-router-dom'; 3 | import { IReduxState } from '../store'; 4 | import { SQLE_BASE_URL } from '../data/common'; 5 | import { globalRouterConfig } from './config'; 6 | 7 | const RouterAuth: React.FC<{ children: React.ReactNode }> = ({ children }) => { 8 | const role = useSelector((state: IReduxState) => state.user.role); 9 | 10 | const location = useLocation(); 11 | 12 | const currentRouter = globalRouterConfig.find( 13 | (v) => `${SQLE_BASE_URL}${v.path}` === location.pathname 14 | ); 15 | 16 | if (currentRouter?.role && !currentRouter.role.includes(role)) { 17 | return ( 18 | 23 | ); 24 | } 25 | 26 | return <>{children}; 27 | }; 28 | 29 | export default RouterAuth; 30 | -------------------------------------------------------------------------------- /src/scripts/version.ts: -------------------------------------------------------------------------------- 1 | export const UI_VERSION = 'feature/issue-110 72a0e52'; 2 | -------------------------------------------------------------------------------- /src/store/auditPlan/index.tsx: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import { IAuditPlanResV2 } from '../../api/common'; 3 | import { ModalStatus } from '../../types/common.type'; 4 | import { commonModalReducer } from '../common'; 5 | type AuditPlanReduxState = { 6 | modalStatus: ModalStatus; 7 | selectAuditPlan: IAuditPlanResV2 | null; 8 | }; 9 | 10 | const initialState: AuditPlanReduxState = { 11 | modalStatus: {}, 12 | selectAuditPlan: null, 13 | }; 14 | const auditPlan = createSlice({ 15 | name: 'auditPlan', 16 | initialState, 17 | reducers: { 18 | ...commonModalReducer(), 19 | updateSelectAuditPlan: ( 20 | state, 21 | { payload: selectedData }: PayloadAction 22 | ) => { 23 | state.selectAuditPlan = selectedData; 24 | }, 25 | }, 26 | }); 27 | export const { 28 | initModalStatus: initAuditPlanModalStatus, 29 | updateModalStatus: updateAuditPlanModalStatus, 30 | updateSelectAuditPlan, 31 | } = auditPlan.actions; 32 | 33 | export default auditPlan.reducer; 34 | -------------------------------------------------------------------------------- /src/store/common/index.ts: -------------------------------------------------------------------------------- 1 | import { PayloadAction } from '@reduxjs/toolkit'; 2 | import { ModalStatus } from '../../types/common.type'; 3 | 4 | export const commonModalReducer = < 5 | T extends { modalStatus: ModalStatus } 6 | >() => ({ 7 | initModalStatus( 8 | state: T, 9 | { payload: { modalStatus } }: PayloadAction<{ modalStatus: ModalStatus }> 10 | ) { 11 | state.modalStatus = modalStatus; 12 | }, 13 | updateModalStatus( 14 | state: T, 15 | { 16 | payload: { modalName, status }, 17 | }: PayloadAction<{ modalName: string; status: boolean }> 18 | ) { 19 | if (Object.prototype.hasOwnProperty.call(state.modalStatus, modalName)) { 20 | state.modalStatus[modalName] = status; 21 | } 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /src/store/globalRuleTemplate/index.test.ts: -------------------------------------------------------------------------------- 1 | import reducers, { updateGlobalSelectRuleTemplate } from '.'; 2 | import { ruleTemplateListData } from '../../page/RuleTemplate/__testData__'; 3 | 4 | describe('store/globalRuleTemplate', () => { 5 | test('should create action', () => { 6 | expect( 7 | updateGlobalSelectRuleTemplate({ ruleTemplate: ruleTemplateListData[0] }) 8 | ).toEqual({ 9 | type: 'globalRuleTemplate/updateGlobalSelectRuleTemplate', 10 | payload: { ruleTemplate: ruleTemplateListData[0] }, 11 | }); 12 | }); 13 | 14 | test('should update select rule template when dispatch updateSelectRuleTemplate action', () => { 15 | const state = { selectGlobalRuleTemplate: null, modalStatus: {} }; 16 | const newState = reducers( 17 | state, 18 | updateGlobalSelectRuleTemplate({ ruleTemplate: ruleTemplateListData[0] }) 19 | ); 20 | expect(newState.selectGlobalRuleTemplate).toBe(ruleTemplateListData[0]); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import locale from './locale'; 3 | import user from './user'; 4 | import userManage from './userManage'; 5 | import whitelist from './whitelist'; 6 | import ruleTemplate from './ruleTemplate'; 7 | import nav from './nav'; 8 | import system from './system'; 9 | import auditPlan from './auditPlan'; 10 | import reportStatistics from './reportStatistics'; 11 | import projectManage from './projectManage'; 12 | import member from './member'; 13 | import globalRuleTemplate from './globalRuleTemplate'; 14 | 15 | const store = configureStore({ 16 | reducer: { 17 | locale, 18 | user, 19 | userManage, 20 | whitelist, 21 | ruleTemplate, 22 | nav, 23 | system, 24 | auditPlan, 25 | reportStatistics, 26 | projectManage, 27 | member, 28 | globalRuleTemplate, 29 | }, 30 | }); 31 | 32 | export type IReduxState = ReturnType; 33 | 34 | export default store; 35 | -------------------------------------------------------------------------------- /src/store/locale/index.test.ts: -------------------------------------------------------------------------------- 1 | import reducers, { updateLanguage } from '.'; 2 | import StorageKey from '../../data/StorageKey'; 3 | import { SupportLanguage } from '../../locale'; 4 | import LocalStorageWrapper from '../../utils/LocalStorageWrapper'; 5 | 6 | describe('store/locale', () => { 7 | test('should create action', () => { 8 | expect(updateLanguage({ language: SupportLanguage.enUS })).toEqual({ 9 | payload: { 10 | language: 'en-US', 11 | }, 12 | type: 'locale/updateLanguage', 13 | }); 14 | }); 15 | 16 | test('should update language when dispatch updateLanguage action', () => { 17 | const localStorageWrapperSpy = jest.spyOn(LocalStorageWrapper, 'set'); 18 | const state = { language: SupportLanguage.zhCN }; 19 | const newState = reducers( 20 | state, 21 | updateLanguage({ language: SupportLanguage.enUS }) 22 | ); 23 | expect(newState.language).toBe(SupportLanguage.enUS); 24 | expect(localStorageWrapperSpy).toBeCalledTimes(1); 25 | expect(localStorageWrapperSpy).toBeCalledWith( 26 | StorageKey.Language, 27 | SupportLanguage.enUS 28 | ); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/store/locale/index.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import StorageKey from '../../data/StorageKey'; 3 | import { SupportLanguage } from '../../locale'; 4 | import LocalStorageWrapper from '../../utils/LocalStorageWrapper'; 5 | 6 | type LocaleReduxState = { 7 | language: string; 8 | }; 9 | 10 | const initialState: LocaleReduxState = { 11 | language: LocalStorageWrapper.getOrDefault( 12 | StorageKey.Language, 13 | SupportLanguage.zhCN 14 | ), 15 | }; 16 | 17 | const locale = createSlice({ 18 | name: 'locale', 19 | initialState, 20 | reducers: { 21 | updateLanguage: ( 22 | state, 23 | { payload: { language } }: PayloadAction<{ language: SupportLanguage }> 24 | ) => { 25 | state.language = language; 26 | LocalStorageWrapper.set(StorageKey.Language, language); 27 | }, 28 | }, 29 | }); 30 | 31 | export const { updateLanguage } = locale.actions; 32 | 33 | export default locale.reducer; 34 | -------------------------------------------------------------------------------- /src/store/nav/index.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { ModalStatus } from '../../types/common.type'; 3 | import { commonModalReducer } from '../common'; 4 | type NavReduxState = { 5 | modalStatus: ModalStatus; 6 | }; 7 | 8 | const initialState: NavReduxState = { 9 | modalStatus: {}, 10 | }; 11 | const nav = createSlice({ 12 | name: 'nav', 13 | initialState, 14 | reducers: { 15 | ...commonModalReducer(), 16 | }, 17 | }); 18 | export const { 19 | initModalStatus: initNavModalStatus, 20 | updateModalStatus: updateNavModalStatus, 21 | } = nav.actions; 22 | 23 | export default nav.reducer; 24 | -------------------------------------------------------------------------------- /src/store/reportStatistics/index.test.ts: -------------------------------------------------------------------------------- 1 | import reducers, { refreshReportStatistics } from '.'; 2 | 3 | describe('store/reportStatistics', () => { 4 | test('should create action', () => { 5 | expect(refreshReportStatistics()).toEqual({ 6 | type: 'reportStatistics/refreshReportStatistics', 7 | }); 8 | }); 9 | 10 | test('should update refresh flag when dispatch refreshReportStatistics action', () => { 11 | const state = { refreshFlag: false }; 12 | const newState = reducers(state, refreshReportStatistics()); 13 | expect(newState.refreshFlag).toBe(!state.refreshFlag); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/store/reportStatistics/index.tsx: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | type ReportStatisticsState = { 4 | refreshFlag: boolean; 5 | }; 6 | 7 | const initialState: ReportStatisticsState = { 8 | refreshFlag: false, 9 | }; 10 | 11 | const reportStatistics = createSlice({ 12 | name: 'reportStatistics', 13 | initialState, 14 | reducers: { 15 | refreshReportStatistics(state) { 16 | state.refreshFlag = !state.refreshFlag; 17 | }, 18 | }, 19 | }); 20 | 21 | export const { refreshReportStatistics } = reportStatistics.actions; 22 | 23 | export default reportStatistics.reducer; 24 | -------------------------------------------------------------------------------- /src/store/ruleTemplate/index.test.ts: -------------------------------------------------------------------------------- 1 | import reducers, { updateSelectRuleTemplate } from '.'; 2 | import { ruleTemplateListData } from '../../page/RuleTemplate/__testData__'; 3 | 4 | describe('store/ruleTemplate', () => { 5 | test('should create action', () => { 6 | expect( 7 | updateSelectRuleTemplate({ ruleTemplate: ruleTemplateListData[0] }) 8 | ).toEqual({ 9 | type: 'ruleTemplate/updateSelectRuleTemplate', 10 | payload: { ruleTemplate: ruleTemplateListData[0] }, 11 | }); 12 | }); 13 | 14 | test('should update select rule template when dispatch updateSelectRuleTemplate action', () => { 15 | const state = { selectRuleTemplate: null, modalStatus: {} }; 16 | const newState = reducers( 17 | state, 18 | updateSelectRuleTemplate({ ruleTemplate: ruleTemplateListData[0] }) 19 | ); 20 | expect(newState.selectRuleTemplate).toBe(ruleTemplateListData[0]); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/store/system/index.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import { SQLE_DEFAULT_WEB_TITLE } from '../../data/common'; 3 | import { ModalStatus } from '../../types/common.type'; 4 | import { commonModalReducer } from '../common'; 5 | 6 | type SystemReduxState = { 7 | modalStatus: ModalStatus; 8 | webTitle: string; 9 | webLogoUrl?: string; 10 | }; 11 | 12 | const initialState: SystemReduxState = { 13 | modalStatus: {}, 14 | webTitle: SQLE_DEFAULT_WEB_TITLE, 15 | }; 16 | 17 | const system = createSlice({ 18 | name: 'system', 19 | initialState, 20 | reducers: { 21 | updateWebTitleAndLog( 22 | state, 23 | { 24 | payload: { webTitle, webLogoUrl }, 25 | }: PayloadAction> 26 | ) { 27 | state.webLogoUrl = webLogoUrl; 28 | state.webTitle = webTitle; 29 | }, 30 | ...commonModalReducer(), 31 | }, 32 | }); 33 | 34 | export const { 35 | updateWebTitleAndLog, 36 | updateModalStatus: updateSystemModalStatus, 37 | initModalStatus: initSystemModalStatus, 38 | } = system.actions; 39 | 40 | export default system.reducer; 41 | -------------------------------------------------------------------------------- /src/store/whitelist/index.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import { IAuditWhitelistResV1 } from '../../api/common.d'; 3 | import { ModalStatus } from '../../types/common.type'; 4 | import { commonModalReducer } from '../common'; 5 | 6 | type WhitelistReduxState = { 7 | modalStatus: ModalStatus; 8 | selectWhitelist: IAuditWhitelistResV1 | null; 9 | }; 10 | 11 | const initialState: WhitelistReduxState = { 12 | selectWhitelist: null, 13 | modalStatus: {}, 14 | }; 15 | 16 | const whitelist = createSlice({ 17 | name: 'whitelist', 18 | initialState, 19 | reducers: { 20 | updateSelectWhitelist( 21 | state, 22 | { 23 | payload: { whitelist }, 24 | }: PayloadAction<{ whitelist: IAuditWhitelistResV1 | null }> 25 | ) { 26 | state.selectWhitelist = whitelist; 27 | }, 28 | ...commonModalReducer(), 29 | }, 30 | }); 31 | 32 | export const { 33 | updateSelectWhitelist, 34 | initModalStatus: initWhitelistModalStatus, 35 | updateModalStatus: updateWhitelistModalStatus, 36 | } = whitelist.actions; 37 | 38 | export default whitelist.reducer; 39 | -------------------------------------------------------------------------------- /src/styles/component.less: -------------------------------------------------------------------------------- 1 | @import './_variable.less'; 2 | 3 | .table-filter-form { 4 | border-bottom: 1px solid @border-gray; 5 | } 6 | 7 | .table-row-cursor { 8 | .ant-table-tbody { 9 | tr { 10 | cursor: pointer; 11 | } 12 | } 13 | } 14 | 15 | .ant-modal-body { 16 | max-height: 70vh; 17 | overflow: auto; 18 | } 19 | 20 | .popconfirm-small { 21 | max-width: 300px; 22 | } 23 | 24 | .ant-modal { 25 | top: 8vh; 26 | } -------------------------------------------------------------------------------- /src/testUtils/mockAntDesignPlots.jsx: -------------------------------------------------------------------------------- 1 | import { cloneDeep, isObject } from 'lodash'; 2 | const MockPlots = (props) => { 3 | const cloneProps = cloneDeep(props); 4 | 5 | Object.keys(cloneProps).forEach((key) => { 6 | if (isObject(cloneProps[key])) { 7 | cloneProps[key] = JSON.stringify(cloneProps[key]); 8 | } 9 | }); 10 | return
; 11 | }; 12 | 13 | const Line = MockPlots; 14 | const Pie = MockPlots; 15 | const RadialBar = MockPlots; 16 | const Gauge = MockPlots; 17 | const Column = MockPlots; 18 | const mockRegisterShape = jest.fn(); 19 | const G2 = { 20 | registerShape: jest.fn(), 21 | }; 22 | 23 | export { Line, Pie, RadialBar, Gauge, Column, G2, mockRegisterShape }; 24 | 25 | // eslint-disable-next-line import/no-anonymous-default-export 26 | export default { 27 | Line, 28 | Pie, 29 | RadialBar, 30 | Gauge, 31 | Column, 32 | G2, 33 | }; 34 | -------------------------------------------------------------------------------- /src/testUtils/mockEditor.jsx: -------------------------------------------------------------------------------- 1 | const mockEditor = (props) => { 2 | // https://github.com/react-monaco-editor/react-monaco-editor/issues/176 3 | const { editorDidMount, ...otherProps } = props; 4 | return ; 5 | }; 6 | 7 | export default mockEditor; 8 | -------------------------------------------------------------------------------- /src/testUtils/mockRedux.tsx: -------------------------------------------------------------------------------- 1 | import { Dictionary } from '../types/common.type'; 2 | import locale from '../store/locale'; 3 | import user from '../store/user'; 4 | import userManage from '../store/userManage'; 5 | import whitelist from '../store/whitelist'; 6 | import nav from '../store/nav'; 7 | import system from '../store/system'; 8 | import React from 'react'; 9 | import { Provider } from 'react-redux'; 10 | import { combineReducers, configureStore } from '@reduxjs/toolkit'; 11 | import projectManage from '../store/projectManage'; 12 | 13 | const reducers = combineReducers({ 14 | locale, 15 | user, 16 | userManage, 17 | whitelist, 18 | nav, 19 | system, 20 | projectManage, 21 | }); 22 | 23 | export const storeFactory = (initStore: Dictionary = {}) => { 24 | return configureStore({ 25 | reducer: reducers, 26 | preloadedState: initStore, 27 | }); 28 | }; 29 | 30 | export const mockUseLocation = () => {}; 31 | 32 | export const CustomProvider: React.FC<{ 33 | initStore: Dictionary; 34 | children: JSX.Element; 35 | }> = (props) => { 36 | return ( 37 | {props.children} 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /src/testUtils/mockStyle.ts: -------------------------------------------------------------------------------- 1 | import * as useStyles from '../theme'; 2 | 3 | export const mockUseStyle = () => { 4 | const useStylesSpy = jest.spyOn(useStyles, 'default'); 5 | useStylesSpy.mockImplementation(() => ({ 6 | loginBg: 'loginBg-test', 7 | headerBg: 'headerBg-test', 8 | editor: 'editor-test', 9 | projectLayoutSider: 'projectLayoutSider-test', 10 | auditResultLevelNormalBox: 'auditResultLevelNormalBox-test', 11 | })); 12 | return useStylesSpy; 13 | }; 14 | -------------------------------------------------------------------------------- /src/theme/dark.ts: -------------------------------------------------------------------------------- 1 | import { createTheme } from '@mui/material/styles'; 2 | 3 | const darkTheme = createTheme({ 4 | common: { 5 | padding: 24, 6 | color: { 7 | warning: '#faad14', 8 | disabledFont: 'rgba(255, 255, 255, .85)', 9 | }, 10 | }, 11 | header: { 12 | background: '#001529', 13 | color: '#fff', 14 | }, 15 | editor: { 16 | border: '1px solid #434343', 17 | }, 18 | projectLayoutSider: { 19 | border: '1px solid #303030', 20 | }, 21 | optionsHover: { 22 | background: 'hsla(0, 0%, 100%, 0.08)', 23 | }, 24 | auditResultLevelNormalBox: { 25 | color: 'rgba(255, 255, 255, .85)', 26 | border: '1px solid rgba(255, 255, 255, .85)', 27 | }, 28 | }); 29 | 30 | export default darkTheme; 31 | -------------------------------------------------------------------------------- /src/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@mui/styles'; 2 | import darkTheme from './dark'; 3 | import lightTheme from './light'; 4 | import { Theme } from '@mui/material/styles'; 5 | 6 | enum SupportTheme { 7 | DARK = 'dark', 8 | LIGHT = 'light', 9 | } 10 | 11 | const ThemeData = { 12 | [SupportTheme.DARK]: darkTheme, 13 | [SupportTheme.LIGHT]: lightTheme, 14 | }; 15 | 16 | const useStyles = makeStyles((theme) => ({ 17 | headerBg: { 18 | backgroundColor: theme.header.background, 19 | color: theme.header.color, 20 | }, 21 | editor: { 22 | border: theme.editor.border, 23 | paddingRight: 1, 24 | }, 25 | projectLayoutSider: { 26 | borderRight: theme.projectLayoutSider.border, 27 | }, 28 | optionsHover: { 29 | '&:hover': { 30 | background: theme.optionsHover.background, 31 | }, 32 | }, 33 | auditResultLevelNormalBox: { 34 | color: theme.auditResultLevelNormalBox.color, 35 | border: theme.auditResultLevelNormalBox.border, 36 | }, 37 | })); 38 | 39 | export { SupportTheme, ThemeData }; 40 | export default useStyles; 41 | -------------------------------------------------------------------------------- /src/theme/light.ts: -------------------------------------------------------------------------------- 1 | import { createTheme } from '@mui/material/styles'; 2 | 3 | const lightTheme = createTheme({ 4 | common: { 5 | padding: 24, 6 | color: { 7 | warning: '#faad14', 8 | disabledFont: 'rgba(0, 0, 0, .85)', 9 | }, 10 | }, 11 | 12 | header: { 13 | background: '#001529', 14 | color: '#fff', 15 | }, 16 | editor: { 17 | border: '1px solid #d9d9d9', 18 | }, 19 | projectLayoutSider: { 20 | border: '1px solid #d9d9d9', 21 | }, 22 | optionsHover: { 23 | background: '#f5f5f5', 24 | }, 25 | auditResultLevelNormalBox: { 26 | color: 'rgba(0, 0, 0, 0.85)', 27 | border: '1px solid rgba(0, 0, 0, 0.85)', 28 | }, 29 | }); 30 | 31 | export default lightTheme; 32 | -------------------------------------------------------------------------------- /src/types/react-i18next.d.ts: -------------------------------------------------------------------------------- 1 | import 'react-i18next'; 2 | 3 | import zhCN from '../locale/zh-CN'; 4 | import enUS from '../locale/en-US'; 5 | 6 | declare module 'react-i18next' { 7 | interface Resources { 8 | 'zh-CN': typeof zhCN.translation; 9 | 'en-US': typeof enUS.translation; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/Copy.ts: -------------------------------------------------------------------------------- 1 | class Copy { 2 | public copyText(text: string) { 3 | const input = document.createElement('input'); 4 | input.style.position = 'fixed'; 5 | input.style.left = '-9999px'; 6 | input.style.top = '-9999px'; 7 | input.style.opacity = '0'; 8 | document.body.appendChild(input); 9 | input.setAttribute('value', text); 10 | input.select(); 11 | if (document.execCommand('copy')) { 12 | document.execCommand('copy'); 13 | } 14 | document.body.removeChild(input); 15 | } 16 | 17 | public copyTextByTextarea(text: string) { 18 | const textarea = document.createElement('textarea'); 19 | textarea.style.position = 'fixed'; 20 | textarea.style.left = '-9999px'; 21 | textarea.style.top = '-9999px'; 22 | textarea.style.opacity = '0'; 23 | document.body.appendChild(textarea); 24 | textarea.value = text; 25 | textarea.select(); 26 | if (document.execCommand('copy')) { 27 | document.execCommand('copy'); 28 | } 29 | document.body.removeChild(textarea); 30 | } 31 | } 32 | 33 | // eslint-disable-next-line import/no-anonymous-default-export 34 | export default new Copy(); 35 | -------------------------------------------------------------------------------- /src/utils/Download.ts: -------------------------------------------------------------------------------- 1 | class Download { 2 | public downloadByCreateElementA(fileStream: any, fileName?: string) { 3 | const url = window.URL.createObjectURL(new Blob([fileStream])); 4 | const link = document.createElement('a'); 5 | link.href = url; 6 | link.setAttribute('download', fileName ? fileName : 'file'); 7 | document.body.appendChild(link); 8 | link.click(); 9 | document.body.removeChild(link); 10 | } 11 | 12 | public downloadBlobFile(blob: Blob, fileName?: string) { 13 | const url = window.URL.createObjectURL(blob); 14 | const link = document.createElement('a'); 15 | link.href = url; 16 | link.setAttribute('download', fileName ? fileName : 'file'); 17 | document.body.appendChild(link); 18 | link.click(); 19 | document.body.removeChild(link); 20 | } 21 | } 22 | 23 | // eslint-disable-next-line import/no-anonymous-default-export 24 | export default new Download(); 25 | -------------------------------------------------------------------------------- /src/utils/FormatterSQL.ts: -------------------------------------------------------------------------------- 1 | import { format } from 'sql-formatter'; 2 | 3 | export enum FormatLanguageSupport { 4 | MySQL = 'mysql', 5 | DB2 = 'db2', 6 | 'SQL Server' = 'tsql', 7 | 'OceanBase For MySQL' = 'mysql', 8 | 'Oracle' = 'plsql', 9 | 'PostgreSQL' = 'postgresql', 10 | } 11 | 12 | export const isSupportLanguage = (type?: string) => { 13 | return Object.keys(FormatLanguageSupport).some((key) => { 14 | return key === type; 15 | }); 16 | }; 17 | 18 | export const formatterSQL = (sql: string, type?: string) => { 19 | let language = 'sql'; 20 | Object.keys(FormatLanguageSupport).forEach((v) => { 21 | const key = v as keyof typeof FormatLanguageSupport; 22 | if (key === type) { 23 | language = FormatLanguageSupport[key]; 24 | } 25 | }); 26 | return format(sql, { 27 | language, 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/utils/HighlightCode.ts: -------------------------------------------------------------------------------- 1 | import hljs from 'highlight.js'; 2 | import sql from 'highlight.js/lib/languages/sql'; 3 | import 'highlight.js/styles/github.css'; 4 | 5 | class HighlightCode { 6 | constructor() { 7 | hljs.registerLanguage('sql', sql); 8 | } 9 | 10 | public highlightSql(sql: string) { 11 | const code = hljs.highlight(sql, { language: 'sql' }); 12 | return code.value; 13 | } 14 | } 15 | 16 | // eslint-disable-next-line import/no-anonymous-default-export 17 | export default new HighlightCode(); 18 | -------------------------------------------------------------------------------- /src/utils/LocalStorageWrapper.ts: -------------------------------------------------------------------------------- 1 | class LocalStorageWrapper { 2 | public set(key: string, value: T) { 3 | return localStorage.setItem(key, value); 4 | } 5 | 6 | public get(key: string) { 7 | return localStorage.getItem(key); 8 | } 9 | 10 | public getOrDefault(key: string, defaultValue: string): string { 11 | if (localStorage.getItem(key) === null) { 12 | return defaultValue; 13 | } 14 | return localStorage.getItem(key) as string; 15 | } 16 | } 17 | 18 | // eslint-disable-next-line import/no-anonymous-default-export 19 | export default new LocalStorageWrapper(); 20 | -------------------------------------------------------------------------------- /src/utils/Math.ts: -------------------------------------------------------------------------------- 1 | export const random = (min: number, max: number): number => { 2 | return Math.random() * (max - min + 1) + min; 3 | }; 4 | 5 | export const floatRound = (num: number, bit = 2): number => { 6 | return Math.round(num * Math.pow(10, bit)) / Math.pow(10, bit); 7 | }; 8 | 9 | export const floatToPercent = (num: number, bit = 2): number => { 10 | return floatRound(num * 100, bit); 11 | }; 12 | 13 | export const minuteToHourMinute = (minutes: number): string => { 14 | if (minutes < 60) { 15 | return minutes + 'min'; 16 | } 17 | return Math.floor(minutes / 60) + 'h' + (minutes % 60) + 'min'; 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/test/FormatSQL.test.ts: -------------------------------------------------------------------------------- 1 | import { formatterSQL, isSupportLanguage } from '../FormatterSQL'; 2 | 3 | describe('test FormatSQL', () => { 4 | test('isSupportLanguage', () => { 5 | expect(isSupportLanguage('MySQL')).toBeTruthy(); 6 | expect(isSupportLanguage('DB2')).toBeTruthy(); 7 | expect(isSupportLanguage('OceanBase For MySQL')).toBeTruthy(); 8 | expect(isSupportLanguage('Oracle')).toBeTruthy(); 9 | expect(isSupportLanguage('PostgreSQL')).toBeTruthy(); 10 | expect(isSupportLanguage('SQL Server')).toBeTruthy(); 11 | expect(isSupportLanguage('TiDB')).toBeFalsy(); 12 | }); 13 | 14 | test('should match snapshot', () => { 15 | expect(formatterSQL('select * from user', 'MySQL')).toMatchSnapshot(); 16 | 17 | expect( 18 | formatterSQL('select GROUP_CONCAT(a) from t', 'MySQL') 19 | ).toMatchSnapshot(); 20 | 21 | expect( 22 | formatterSQL('select GROUP_CONCAT(a) from t', 'DB2') 23 | ).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/utils/test/__snapshots__/FormatSQL.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test FormatSQL should match snapshot 1`] = ` 4 | "select 5 | * 6 | from 7 | user" 8 | `; 9 | 10 | exports[`test FormatSQL should match snapshot 2`] = ` 11 | "select 12 | GROUP_CONCAT(a) 13 | from 14 | t" 15 | `; 16 | 17 | exports[`test FormatSQL should match snapshot 3`] = ` 18 | "select 19 | GROUP_CONCAT (a) 20 | from 21 | t" 22 | `; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------