From 0d3afd446ad00630982495dd3713ddcfb9f2b3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Youngster=5F=E7=A8=8B?= <55881562+youngster-yj@users.noreply.github.com> Date: Fri, 6 Dec 2024 16:29:14 +0800 Subject: [PATCH] Yj/feat/audit code2.0 (#2172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:新增组件YakitVirtualList * style:增加overflow:hidden * feat: 新增项目管理 * feat: 审计功能扩展 * feat: 代码审计新流程处理 * feat: 暂存代码 * feat: 审计交互逻辑扩展 * feat: 图标替换与功能补充 * fix: 右部跳转逻辑完善 * feat: 审计代码段扩展为Codemirror展示 * feature(protoc):get json schema from backend * feat: JsonSchema构建完成 * protoc: add SSAProgram and scan manage API * feat: 项目管理接口接入 * feat: JsonSchema应用扩展 * fix: JsonSchema数据收集处理 * fix: 继续扩展 JsonSchema数据收集 * feat: 双流暂存 * feat: 代码扫描接入编译 * feat: yakrunner在远程模式下 支持手动输入路径 * fix: 上传文案更改 * protoc:modify protoc remove useless method * feat: 新增项目管理\代码审计-重新编译 * fix: 项目管理列表排序处理 * fix: 细节打磨 1.解决字体大小 2.解决布局未对齐 3.点完开始后把参数收起 4.跳转切换为已编译项目 5.进度条日志进度条 6.项目关联删除后重置选择项 * fix: 细节收尾 * fix: 删除无关依赖项 --------- Co-authored-by: luoluo <1425735414@qq.com> Co-authored-by: wlz --- app/main/handlers/syntaxFlow.js | 60 +- app/main/handlers/yakRunnerTerminal.js | 3 + app/protos/grpc.proto | 36 +- app/renderer/src/main/package.json | 5 + .../src/main/src/assets/icon/colors.tsx | 522 +++++++ .../src/main/src/assets/icon/outline.tsx | 58 + .../JsonFormWrapper.module.scss | 64 + .../JsonFormWrapper/JsonFormWrapper.tsx | 451 ++++++ .../MessageCenter/MessageCenter.tsx | 2 - .../main/src/components/layout/UILayout.tsx | 2 + .../yakCodemirror/YakCodemirror.module.scss | 41 + .../yakCodemirror/YakCodemirror.moduleType.ts | 20 + .../yakCodemirror/YakCodemirror.tsx | 173 +++ .../YakitAutoComplete.module.scss | 1 + .../yakitUI/YakitForm/YakitForm.tsx | 10 +- .../yakitUI/YakitForm/YakitFormType.d.ts | 2 + .../YakitTextArea/YakitTextArea.module.scss | 193 --- .../yakitUI/YakitTextArea/YakitTextArea.tsx | 401 ------ .../YakitVirtualList.module.scss | 160 +++ .../YakitVirtualList/YakitVirtualList.tsx | 384 +++++ .../YakitVirtualListType.d.ts | 52 + .../src/main/src/constants/software.ts | 12 + app/renderer/src/main/src/enums/yakitRoute.ts | 4 +- .../src/main/src/pages/SetPassword.tsx | 1 - .../YakRunnerProjectManager.module.scss | 13 + .../YakRunnerProjectManager.tsx | 58 + .../YakRunnerProjectManagerType.d.ts | 1 + .../src/pages/invoker/YakitLogFormatter.tsx | 2 +- .../src/pages/layout/publicMenu/MenuMode.tsx | 13 + .../src/pages/mitm/MITMYakScriptLoader.tsx | 14 +- .../pages/payloadManager/newPayloadTable.tsx | 1 - .../pluginEditor/editorCode/EditorCode.tsx | 26 +- .../LocalPluginExecuteDetailHeard.module.scss | 2 +- .../LocalPluginExecuteDetailHeard.tsx | 60 +- .../LocalPluginExecuteDetailHeardType.d.ts | 16 +- .../PluginExecuteExtraParams.tsx | 14 +- .../PluginExecuteResult.tsx | 6 +- .../PluginExecuteResultType.d.ts | 1 + .../main/src/pages/plugins/pluginsType.d.ts | 1 + .../reverseShellReceiver/shellReceiver.tsx | 1 - .../YakitRiskTable/YakitRiskTable.module.scss | 7 - .../risks/YakitRiskTable/YakitRiskTable.tsx | 19 +- .../BottomEditorDetails.tsx | 16 +- .../SyntaxCheckList/SyntaxCheckList.tsx | 2 - .../TerminalBox/TerminalBox.tsx | 4 - .../RunnerFileTree/RunnerFileTree.tsx | 83 +- .../RunnerFileTree/RunnerFileTreeType.d.ts | 8 +- .../pages/yakRunner/RunnerTabs/RunnerTabs.tsx | 16 +- .../main/src/pages/yakRunner/YakRunner.tsx | 16 +- .../AuditCode/AuditCode.module.scss | 186 ++- .../AuditCode/AuditCode.tsx | 1268 ++++++++++++----- .../AuditCode/AuditCodeType.d.ts | 79 +- .../BottomEditorDetails.module.scss | 7 + .../BottomEditorDetails.tsx | 109 +- .../BottomEditorDetailsType.d.ts | 2 +- .../RuleEditorBox/RuleEditorBox.tsx | 78 + .../SyntaxCheckList.module.scss | 48 - .../SyntaxCheckList/SyntaxCheckList.tsx | 85 -- .../BottomSideBar/BottomSideBar.tsx | 97 +- .../LeftAudit/LeftAudit.tsx | 51 +- .../RightAuditDetail/RightAuditDetail.tsx | 54 +- .../RunnerFileTree/RunnerFileTree.module.scss | 2 +- .../RunnerFileTree/RunnerFileTree.tsx | 83 +- .../RunnerFileTree/RunnerFileTreeType.d.ts | 1 + .../RunnerTabs/RunnerTabs.tsx | 60 +- .../RunnerTabs/RunnerTabsType.d.ts | 3 +- .../YakRunnerAuditCode.module.scss | 2 + .../yakRunnerAuditCode/YakRunnerAuditCode.tsx | 157 +- .../hooks/YakRunnerContext.ts | 12 +- .../src/pages/yakRunnerAuditCode/utils.ts | 18 +- .../AuditCodeDetailDrawer.tsx | 8 +- .../CodeScanResultTable.tsx | 1 - .../YakRunnerCodeScan.module.scss | 82 +- .../yakRunnerCodeScan/YakRunnerCodeScan.tsx | 766 +++++++--- .../YakRunnerCodeScanType.d.ts | 46 +- app/renderer/src/main/src/routes/newRoute.tsx | 26 +- .../src/main/src/routes/privateIcon.tsx | 73 + .../src/main/src/routes/publicIcon.tsx | 27 + app/renderer/src/main/src/store/pageInfo.ts | 2 +- .../utils/eventBus/events/yakRunnerAudit.ts | 8 +- app/renderer/src/main/yarn.lock | 203 ++- 81 files changed, 4867 insertions(+), 1834 deletions(-) create mode 100644 app/renderer/src/main/src/components/JsonFormWrapper/JsonFormWrapper.module.scss create mode 100644 app/renderer/src/main/src/components/JsonFormWrapper/JsonFormWrapper.tsx create mode 100644 app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.module.scss create mode 100644 app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.moduleType.ts create mode 100644 app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.tsx delete mode 100644 app/renderer/src/main/src/components/yakitUI/YakitTextArea/YakitTextArea.module.scss delete mode 100644 app/renderer/src/main/src/components/yakitUI/YakitTextArea/YakitTextArea.tsx create mode 100644 app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualList.module.scss create mode 100644 app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualList.tsx create mode 100644 app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualListType.d.ts create mode 100644 app/renderer/src/main/src/constants/software.ts create mode 100644 app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManager.module.scss create mode 100644 app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManager.tsx create mode 100644 app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManagerType.d.ts create mode 100644 app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/RuleEditorBox/RuleEditorBox.tsx delete mode 100644 app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.module.scss delete mode 100644 app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.tsx diff --git a/app/main/handlers/syntaxFlow.js b/app/main/handlers/syntaxFlow.js index 55160f8c31..b02fad340a 100644 --- a/app/main/handlers/syntaxFlow.js +++ b/app/main/handlers/syntaxFlow.js @@ -1,4 +1,4 @@ -const {ipcMain} = require("electron"); +const {ipcMain} = require("electron") module.exports = (win, getClient) => { const asyncQuerySyntaxFlowRuleGroup = (params) => { @@ -34,16 +34,16 @@ module.exports = (win, getClient) => { }) // 规则执行 - const handlerHelper = require("./handleStreamWithContext"); - const streamSyntaxFlowScanMap = new Map(); - ipcMain.handle("cancel-SyntaxFlowScan", handlerHelper.cancelHandler(streamSyntaxFlowScanMap)); + const handlerHelper = require("./handleStreamWithContext") + const streamSyntaxFlowScanMap = new Map() + ipcMain.handle("cancel-SyntaxFlowScan", handlerHelper.cancelHandler(streamSyntaxFlowScanMap)) ipcMain.handle("SyntaxFlowScan", (e, params, token) => { let stream = streamSyntaxFlowScanMap.get(token) if (stream) { stream.write(params) return } - stream = getClient().SyntaxFlowScan(); + stream = getClient().SyntaxFlowScan() stream.write(params) handlerHelper.registerHandler(win, stream, streamSyntaxFlowScanMap, token) }) @@ -63,4 +63,52 @@ module.exports = (win, getClient) => { ipcMain.handle("QuerySyntaxFlowResult", async (e, params) => { return await asyncQuerySyntaxFlowResult(params) }) -} \ No newline at end of file + + const asyncQuerySSAPrograms = (params) => { + return new Promise((resolve, reject) => { + getClient().QuerySSAPrograms(params, (err, data) => { + if (err) { + reject(err) + return + } + resolve(data) + }) + }) + } + // 获取项目管理列表 + ipcMain.handle("QuerySSAPrograms", async (e, params) => { + return await asyncQuerySSAPrograms(params) + }) + + const asyncUpdateSSAProgram = (params) => { + return new Promise((resolve, reject) => { + getClient().UpdateSSAProgram(params, (err, data) => { + if (err) { + reject(err) + return + } + resolve(data) + }) + }) + } + // 更新项目管理数据 + ipcMain.handle("UpdateSSAProgram", async (e, params) => { + return await asyncUpdateSSAProgram(params) + }) + + const asyncDeleteSSAPrograms = (params) => { + return new Promise((resolve, reject) => { + getClient().DeleteSSAPrograms(params, (err, data) => { + if (err) { + reject(err) + return + } + resolve(data) + }) + }) + } + // 删除项目管理数据 + ipcMain.handle("DeleteSSAPrograms", async (e, params) => { + return await asyncDeleteSSAPrograms(params) + }) +} diff --git a/app/main/handlers/yakRunnerTerminal.js b/app/main/handlers/yakRunnerTerminal.js index 9fe76ece04..cfcebffd61 100644 --- a/app/main/handlers/yakRunnerTerminal.js +++ b/app/main/handlers/yakRunnerTerminal.js @@ -54,6 +54,9 @@ module.exports = (win, getClient) => { // 如果有问题,重置 stream.on("error", (e) => { removeStreamRunner(id) + if(win){ + win.webContents.send("client-listening-terminal-error", {id, path}) + } }) // 发送回数据 diff --git a/app/protos/grpc.proto b/app/protos/grpc.proto index dfd3f5d0a6..df0beb3aca 100644 --- a/app/protos/grpc.proto +++ b/app/protos/grpc.proto @@ -5554,29 +5554,30 @@ message SSAProgram{ string Language = 6; string EngineVersion = 7; - // need re-compile + // need re-compile bool Recompile = 8; - // risk number + // risk number int64 HighRiskNumber = 9; int64 CriticalRiskNumber = 10; int64 WarnRiskNumber = 11; int64 LowRiskNumber = 12; + uint32 Id = 13; } message SSAProgramInput { - string Name = 1; // index - string Description = 2; + string Name = 1; // index + string Description = 2; } message SSAProgramFilter{ - repeated string ProgramNames = 1; - repeated string Languages = 2; - repeated int64 Ids = 3; + repeated string ProgramNames = 1; + repeated string Languages = 2; + repeated int64 Ids = 3; // update range int64 BeforeUpdatedAt = 5; int64 AfterUpdatedAt = 6; - // fuzz search + // fuzz search string Keyword = 7 ; // id range int64 AfterID = 8; @@ -5585,10 +5586,19 @@ message SSAProgramFilter{ message QuerySSAProgramRequest{ - Paging Paging = 1; + Paging Paging = 1; // abort + Paging Pagination = 2; SSAProgramFilter Filter = 3; } +message QuerySSAProgramResponse{ + Paging Paging = 1; // abort + Paging Pagination = 5; + repeated SSAProgram Programs = 2; // abort + repeated SSAProgram Data = 4; + int64 Total = 3; +} + message UpdateSSAProgramRequest{ SSAProgramInput ProgramInput = 1; } @@ -5598,11 +5608,6 @@ message DeleteSSAProgramRequest{ SSAProgramFilter Filter = 2; } -message QuerySSAProgramResponse{ - Paging Paging = 1; - repeated SSAProgram Programs = 2; - int64 Total = 3; -} message CreateSyntaxFlowRuleRequest{ SyntaxFlowRuleInput SyntaxFlowInput = 1; @@ -5646,7 +5651,7 @@ message SyntaxFlowScanRequest{ // 启动扫描任务 SyntaxFlowRuleFilter Filter = 2; // 用于指定扫描的规则 - repeated string ProgramName = 3; // 用于指定扫描的程序 + repeated string ProgramName = 3; // 用于指定扫描的程序 // 恢复扫描任务 string ResumeTaskId = 5; // 恢复任务ID @@ -5654,6 +5659,7 @@ message SyntaxFlowScanRequest{ bool IgnoreLanguage = 4; // 是否忽略语言 默认为false 将会只运行和当前项目语言一致的规则,最后运行的规则可能会比当前选中的规则少一些。 } + message QuerySyntaxFlowScanTaskRequest{ Paging Pagination = 1; SyntaxFlowScanTaskFilter Filter = 2; diff --git a/app/renderer/src/main/package.json b/app/renderer/src/main/package.json index c156eb1306..be79d54e2c 100644 --- a/app/renderer/src/main/package.json +++ b/app/renderer/src/main/package.json @@ -11,6 +11,10 @@ "@dnd-kit/core": "^4.0.1", "@hello-pangea/dnd": "16.5.0", "@prettier/plugin-xml": "^3.2.2", + "@rjsf/antd": "^5.22.4", + "@rjsf/core": "^5.22.4", + "@rjsf/utils": "^5.22.4", + "@rjsf/validator-ajv8": "^5.22.4", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", @@ -32,6 +36,7 @@ "axios": "^1.4.0", "bizcharts": "^4.1.12", "classnames": "^2.3.2", + "codemirror": "^5.65.2", "cross-env": "7.0.3", "customize-cra": "^1.0.0", "diff": "^7.0.0", diff --git a/app/renderer/src/main/src/assets/icon/colors.tsx b/app/renderer/src/main/src/assets/icon/colors.tsx index 0583c39d7e..d4238fa624 100644 --- a/app/renderer/src/main/src/assets/icon/colors.tsx +++ b/app/renderer/src/main/src/assets/icon/colors.tsx @@ -1121,3 +1121,525 @@ const LogNodeStatusDelete = () => ( export const LogNodeStatusDeleteIcon = (props: Partial) => { return } + +const RuleManagementAudit = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) + +/** + * @description 规则管理、代码审计图标 + */ +export const RuleManagementAuditIcon = (props: Partial) => { + return +} diff --git a/app/renderer/src/main/src/assets/icon/outline.tsx b/app/renderer/src/main/src/assets/icon/outline.tsx index a11fc9e041..469b619609 100644 --- a/app/renderer/src/main/src/assets/icon/outline.tsx +++ b/app/renderer/src/main/src/assets/icon/outline.tsx @@ -5542,3 +5542,61 @@ const PositionIcon = () => ( export const OutlinePositionIcon = (props: Partial) => { return } + +const ReloadScanIcon = () => ( + + + + + + +) + +export const OutlineReloadScanIcon = (props: Partial) => { + return +} + +const ScanRuleEditIcon = () => ( + + + + + + +) + +export const OutlineScanRuleEditIcon = (props: Partial) => { + return +} \ No newline at end of file diff --git a/app/renderer/src/main/src/components/JsonFormWrapper/JsonFormWrapper.module.scss b/app/renderer/src/main/src/components/JsonFormWrapper/JsonFormWrapper.module.scss new file mode 100644 index 0000000000..3dc22a89d7 --- /dev/null +++ b/app/renderer/src/main/src/components/JsonFormWrapper/JsonFormWrapper.module.scss @@ -0,0 +1,64 @@ +.json-schema-box { + :global { + .has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input:focus, + .has-error + :not(.ant-input-affix-wrapper-disabled):not( + .ant-input-affix-wrapper-borderless + ).ant-input-affix-wrapper:focus, + .has-error :not(.ant-input-disabled):not(.ant-input-borderless).ant-input-focused, + .has-error + :not(.ant-input-affix-wrapper-disabled):not( + .ant-input-affix-wrapper-borderless + ).ant-input-affix-wrapper-focused { + border-color: #ff4d4f; + // box-shadow: 0 0 0 2px var(--input-box-shadow-color); + } + + .ant-form-item { + // display: flex; + // flex-direction: row; + // .ant-form-item-label { + // flex: 8; + // text-align: right; + // label::after { + // content: ":"; + // position: relative; + // top: -0.5px; + // margin: 0 8px 0 2px; + // z-index: 1; + // display: inline-flex; + // align-items: center; + // } + // } + // .ant-form-item-control { + // flex: 16; + // } + + // .ant-form-item-label { + // // 仅在相邻元素不是 .ant-form-item-control 时应用样式 + // &:not(:has(+ .ant-form-item-control)) { + // display: none; + // width: 100%; + // padding: 0; + // margin-bottom: 20px; + // text-align: left; + // border: 0; + // border-bottom: 1px solid #e5e5e5; + // label{ + // font-size: 21px !important; + // color: #333; + // } + // label::after { + // content: ""; + // position: relative; + // top: -0.5px; + // margin: 0 8px 0 2px; + // z-index: 1; + // display: inline-flex; + // align-items: center; + // } + // } + // } + } + } +} \ No newline at end of file diff --git a/app/renderer/src/main/src/components/JsonFormWrapper/JsonFormWrapper.tsx b/app/renderer/src/main/src/components/JsonFormWrapper/JsonFormWrapper.tsx new file mode 100644 index 0000000000..481653e318 --- /dev/null +++ b/app/renderer/src/main/src/components/JsonFormWrapper/JsonFormWrapper.tsx @@ -0,0 +1,451 @@ +import React, {useEffect, useMemo, useRef, useState} from "react" +import styles from "./JsonFormWrapper.module.scss" +import validator from "@rjsf/validator-ajv8" // 添加这行 +import JsonForm from "@rjsf/antd" +import { + ErrorSchema, + FieldProps, + labelValue, + RegistryFieldsType, + RegistryWidgetsType, + UiSchema, + WidgetProps +} from "@rjsf/utils" +import {YakitSelect} from "../yakitUI/YakitSelect/YakitSelect" +import {YakitButton} from "../yakitUI/YakitButton/YakitButton" +import {YakitInput} from "../yakitUI/YakitInput/YakitInput" +import {YakitDragger} from "../yakitUI/YakitForm/YakitForm" +import {useGetState, useMemoizedFn, useUpdateEffect} from "ahooks" +import {YakitInputNumber} from "../yakitUI/YakitInputNumber/YakitInputNumber" +import {Checkbox, Form, FormInstance} from "antd" +import {YakitCheckbox} from "../yakitUI/YakitCheckbox/YakitCheckbox" +import {YakitSwitch} from "../yakitUI/YakitSwitch/YakitSwitch" +import {YakitRadioButtons} from "../yakitUI/YakitRadioButtons/YakitRadioButtons" +import classNames from "classnames" + +export const getJsonSchemaListResult = (obj: {[key: string]: any}) => { + // 此处的key用于筛选重复的表单数据 + let keyError: string[] = [] + let jsonSchemaError: JsonFormValidateProps[] = [] + let keySuccess: string[] = [] + let jsonSchemaSuccess: JsonFormValidateProps[] = [] + + Object.keys(obj).forEach((key) => { + const result: JsonFormValidateProps = obj[key]() + if (result.pass) { + if (!keySuccess.includes(result.key)) { + jsonSchemaSuccess.push(result) + keySuccess.push(result.key) + } + } else { + if (!keyError.includes(result.key)) { + jsonSchemaError.push(result) + keyError.push(result.key) + } + } + }) + + return { + jsonSchemaError, + jsonSchemaSuccess + } +} + +export interface JsonFormValidateProps { + pass: boolean + error?: any[] + key: string + value: string +} + +export interface JsonFormSchemaListWrapper { + /** JsonSchema数据收集(PS:此处可能存在多个内部Form因此采用Ref数组的形式依次校验) */ + jsonSchemaListRef?: React.MutableRefObject<{ + [key: string]: any + }> +} + +export interface JsonFormWrapperProps { + jsonSchemaListRef: React.MutableRefObject<{ + [key: string]: any + }> + field: string + value?: string + onChange?: (v: string) => void + schema: any + disabled?: boolean +} + +/** 创建一个包装组件来处理 JsonForm */ +export const JsonFormWrapper: React.FC = React.memo((props) => { + const {jsonSchemaListRef, field, value, schema, disabled} = props + + const [formData, setFormData, getFormData] = useGetState(value || {}) + const jsonSchemaRef = useRef() + + useEffect(() => { + if (jsonSchemaListRef.current) { + if (!jsonSchemaListRef.current.hasOwnProperty(field)) { + jsonSchemaListRef.current = { + ...jsonSchemaListRef.current, + [field]: validate + } + } + } + }, []) + + // 调用校验是否错误 + const validate = () => { + try { + const result = jsonSchemaRef.current?.validate(getFormData()) + // console.log("result?.errors", result?.errors) + // console.log("formData---", field, getFormData()) + + return { + pass: (result?.errors || []).length === 0, + error: result?.errors, + key: field, + value: getFormData() + } as JsonFormValidateProps + } catch (error) { + console.error("JsonForm validation error:", error) + return { + pass: false, + key: field, + value: getFormData() + } as JsonFormValidateProps + } + } + + useUpdateEffect(() => { + // 当外部 value 变化时更新内部状态 + setFormData(value) + }, [value]) + + // const UploadFolderPath = useMemoizedFn((props: FieldProps) => { + // const {formData, disabled, onChange} = props + // return ( + // <> + // ) + // }) + + // const fields: RegistryFieldsType = { + // "/test": UploadFolderPath, + // } + + const uiSchema: UiSchema = Object.keys(schema.properties || {}).reduce((acc, key) => { + // 是否显示字段的 label + acc[key] = { + "ui:label": true + } + return acc + }, {}) + + const getTextWidget = useMemoizedFn((props: WidgetProps) => { + const {id, required, readonly, disabled, value, onChange, onBlur, onFocus, autofocus, options, schema} = props + if (schema.type === "number") { + return ( + { + onChange(value) + }} + onBlur={(value) => { + onBlur(id, value) + }} + onFocus={(value) => { + onFocus(id, value) + }} + /> + ) + } else if (schema.yakit_type === "upload-folder-path") { + return ( + { + onChange(value === "" ? options.emptyValue : value) + }} + /> + ) + } else if (schema.yakit_type === "upload-path") { + return ( + { + onChange(value === "" ? options.emptyValue : value) + }} + /> + ) + } + + return ( + { + onChange(event.target.value === "" ? options.emptyValue : event.target.value) + }} + onBlur={(event) => { + onBlur(id, event.target.value) + }} + onFocus={(event) => { + onFocus(id, event.target.value) + }} + /> + ) + }) + + const getTextareaWidget = useMemoizedFn((props: WidgetProps) => { + const {id, placeholder, value, required, disabled, autofocus, readonly, onBlur, onFocus, onChange, options} = + props + + return ( + { + onChange(event.target.value === "" ? options.emptyValue : event.target.value) + }} + onBlur={(event) => { + onBlur(id, event.target.value) + }} + onFocus={(event) => { + onFocus(id, event.target.value) + }} + /> + ) + }) + + const getSelectWidget = useMemoizedFn((props: WidgetProps) => { + const {id, options, multiple, disabled, readonly, value, autofocus, onChange, onBlur, onFocus} = props + const {enumOptions = [], enumDisabled} = options + let mode: any = multiple ? "multiple" : "default" + mode = options.mode || mode + return ( + onChange(value)} + onBlur={(value) => onBlur(id, value)} + onFocus={(value) => onFocus(id, value)} + > + {enumOptions.map(({value, label}: any, i: number) => { + const disabled: any = enumDisabled && enumDisabled.indexOf(value) !== -1 + + return ( + + {label} + + ) + })} + + ) + }) + + // 应后端要求 此处替换控件为switch + const getSwitchWidget = useMemoizedFn((props: WidgetProps) => { + const {label, value, disabled, readonly, autofocus, onChange}: WidgetProps = props + return ( +
+
+ +
+
+ onChange(value)} + /> +
+
+ ) + }) + + const getCheckboxesWidget = useMemoizedFn((props: WidgetProps) => { + const {id, options, value, disabled, readonly, autofocus, onChange}: WidgetProps = props + const {enumOptions, enumDisabled} = options + return ( + onChange(value)}> + {(enumOptions as any[]).map((option: any, index: number) => { + const checked: boolean = value.indexOf(option.value) !== -1 + const itemDisabled: any = enumDisabled && (enumDisabled as string[]).indexOf(option.value) !== -1 + return ( + + {option.label} + + ) + })} + + ) + }) + + const getPasswordWidget = useMemoizedFn((props: WidgetProps) => { + const {id, required, readonly, disabled, value, onFocus, onBlur, onChange, options, autofocus} = props + return ( + { + onChange(event.target.value === "" ? options.emptyValue : event.target.value) + }} + onBlur={(event) => { + onBlur(id, event.target.value) + }} + onFocus={(event) => { + onFocus(id, event.target.value) + }} + /> + ) + }) + + const getRadioWidget = useMemoizedFn((props: WidgetProps) => { + const {options, value, disabled, readonly, onChange} = props + const {enumOptions, enumDisabled} = options + return ( + { + onChange(e.target.value) + }} + buttonStyle='solid' + options={(enumOptions as object[]).map((option: any) => { + const itemDisabled: any = enumDisabled && (enumDisabled as string[]).indexOf(option.value) !== -1 + const info = { + value: option.value, + label: option.label, + disabled: itemDisabled + } + return info + })} + /> + ) + }) + + const getUpDownWidget = useMemoizedFn((props: WidgetProps) => { + const {id, required, readonly, disabled, value, onChange, onBlur, onFocus, autofocus, options, schema} = props + if (schema.multipleOf) { + options.step = schema.multipleOf + } + + if (typeof schema.minimum !== "undefined") { + options.min = schema.minimum + } + + if (typeof schema.maximum !== "undefined") { + options.max = schema.maximum + } + return ( + onChange(value)} + onBlur={(value) => onBlur(id, value)} + onFocus={(value) => onFocus(id, value)} + /> + ) + }) + + const AntdForm = useMemoizedFn((props) => { + return
+ }) + + return ( + <> + { + // 更新表单值 + setFormData(e.formData) + }} + /** + * 如果omitExtraData和liveOmit都被设置为true,那么当onChange被调用时, + * 不在任何表单字段中的额外表单数据值将被删除。默认为false。 + * */ + omitExtraData={true} + liveOmit={true} + // 如果设置为true,表单将在更改表单数据时执行验证并显示任何验证错误,而不仅仅是在提交时。 + liveValidate={true} + /** + * 当此属性设置为top或bottom时,错误列表(或ErrorList中定义的自定义错误列表)也将显示在表单的底部或顶部。 + * 当设置为false时,只显示内联输入验证错误。默认设置为top。 + * */ + showErrorList={false} + onSubmit={(e) => {}} + // 添加错误处理 + onError={(errors) => {}} + > + {/* 这行代码会移除默认的提交按钮 */} + <> + + + ) +}) diff --git a/app/renderer/src/main/src/components/MessageCenter/MessageCenter.tsx b/app/renderer/src/main/src/components/MessageCenter/MessageCenter.tsx index f2ecee2616..8c3d2d2758 100644 --- a/app/renderer/src/main/src/components/MessageCenter/MessageCenter.tsx +++ b/app/renderer/src/main/src/components/MessageCenter/MessageCenter.tsx @@ -1,6 +1,4 @@ import React, {useEffect, useMemo, useRef, useState} from "react" -import {} from "antd" -import {} from "@ant-design/icons" import {useGetState, useInterval, useMemoizedFn, useSize, useThrottleFn, useVirtualList} from "ahooks" import {NetWorkApi} from "@/services/fetch" import {API} from "@/services/swagger/resposeType" diff --git a/app/renderer/src/main/src/components/layout/UILayout.tsx b/app/renderer/src/main/src/components/layout/UILayout.tsx index 984a240a17..14a106446b 100644 --- a/app/renderer/src/main/src/components/layout/UILayout.tsx +++ b/app/renderer/src/main/src/components/layout/UILayout.tsx @@ -82,6 +82,7 @@ import {grpcFetchLatestYakVersion, grpcFetchYakInstallResult} from "@/apiUtils/g import {NetWorkApi} from "@/services/fetch" import {API} from "@/services/swagger/resposeType" import {visitorsStatisticsFun} from "@/utils/visitorsStatistics" +import { setYakitEngineMode } from "@/constants/software" const {ipcRenderer} = window.require("electron") @@ -134,6 +135,7 @@ const UILayout: React.FC = (props) => { const [engineMode, setEngineMode] = useState() const cacheEngineMode = useRef() const onSetEngineMode = useMemoizedFn((v?: YaklangEngineMode) => { + setYakitEngineMode(v) setEngineMode(v) cacheEngineMode.current = v }) diff --git a/app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.module.scss b/app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.module.scss new file mode 100644 index 0000000000..5969c51149 --- /dev/null +++ b/app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.module.scss @@ -0,0 +1,41 @@ +.yak-codemirror { + .highlight-text { + background-color: #fff3b2; // 浅黄色背景 + // 或者使用其他样式 + // border-bottom: 2px solid #f00; // 下划线 + // font-weight: bold; // 加粗 + } + :global { + // 禁用文本选择 + .CodeMirror { + pre.CodeMirror-line, + pre.CodeMirror-line-like { + font-family: "Courier New", Courier, monospace; + } + + height: auto !important; // 关键设置 + // 设置最小高度(可选) + min-height: 100px; + // 设置最大高度(可选) + max-height: 300px; + user-select: none; + -webkit-user-select: none; + } + + // 移除只读模式下的背景色 + .CodeMirror-readonly { + background-color: transparent; + } + + // 优化滚动条样式 + .CodeMirror-scrollbar-filler { + background-color: transparent; + } + + .CodeMirror-simplescroll-horizontal, + .CodeMirror-simplescroll-vertical { + background-color: #f5f5f5; + border: none; + } + } +} diff --git a/app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.moduleType.ts b/app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.moduleType.ts new file mode 100644 index 0000000000..d77e1169e5 --- /dev/null +++ b/app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.moduleType.ts @@ -0,0 +1,20 @@ +// 添加高亮范围的类型 +interface HighlightRange { + from: { line: number; ch: number } + to: { line: number; ch: number } + className?: string + } + +export interface YakCodemirrorProps { + value: string + onChange?: (v:string) => void + language?: string + readOnly?: boolean + // 如若指定文件名 则根据文件名解析语言模式 + fileName?: string + theme?: string + // 指定起始行号 + firstLineNumber?: number + /** @name 配置项-高亮显示配置 */ + highLight?: HighlightRange +} diff --git a/app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.tsx b/app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.tsx new file mode 100644 index 0000000000..7ee67774b1 --- /dev/null +++ b/app/renderer/src/main/src/components/yakCodemirror/YakCodemirror.tsx @@ -0,0 +1,173 @@ +import React, {useEffect, useMemo, useRef, useState} from "react" +import styles from "./YakCodemirror.module.scss" +import {YakCodemirrorProps} from "./YakCodemirror.moduleType" +import {Controlled as CodeMirror} from "react-codemirror2" + +// 正确的样式导入方式 +import "codemirror/lib/codemirror.css" +// 主题样式 +// import "codemirror/theme/material.css" +// import "codemirror/theme/eclipse.css" // Eclipse主题 +// import "codemirror/theme/xq-light.css" // XQ Light主题 +// import "codemirror/theme/idea.css" // IntelliJ IDEA主题 +import "codemirror/theme/solarized.css" // Solarized主题 +// 语言模式 +import "codemirror/mode/javascript/javascript" +import "codemirror/mode/xml/xml" +import "codemirror/mode/css/css" +import "codemirror/mode/python/python" +import "codemirror/mode/markdown/markdown" +import "codemirror/mode/php/php" +import "codemirror/mode/ruby/ruby" +import "codemirror/mode/shell/shell" +import "codemirror/mode/sql/sql" +import "codemirror/mode/yaml/yaml" +import "codemirror/mode/dockerfile/dockerfile" +import "codemirror/mode/htmlmixed/htmlmixed" +import "codemirror/mode/clike/clike" // java, c, cpp等 +// 插件 +import "codemirror/addon/hint/show-hint" +import "codemirror/addon/hint/javascript-hint" +import "codemirror/addon/hint/show-hint.css" +import "codemirror/addon/selection/active-line" +import "codemirror/addon/edit/matchbrackets" + +export const YakCodemirror: React.FC = (props) => { + const { + value, + onChange, + fileName, + readOnly = false, + language = "javascript", + theme = "solarized", + highLight, + firstLineNumber = 1 + } = props + const [codemirrorEditor, setCodemirrorEditor] = useState() + + // 根据文件后缀判断语言模式 + const getLanguageMode = (filename: string) => { + const extension = filename.toLowerCase().split(".").pop() || "" + const modeMap: {[key: string]: string} = { + // JavaScript 相关 + js: "javascript", + jsx: "javascript", + ts: "text/typescript", + tsx: "text/typescript-jsx", + + // Web 相关 + html: "htmlmixed", + htm: "htmlmixed", + css: "css", + less: "text/x-less", + scss: "text/x-scss", + sass: "text/x-scss", + + // 后端语言 + py: "python", + python: "python", + java: "text/x-java", + cpp: "text/x-c++src", + c: "text/x-csrc", + cs: "text/x-csharp", + php: "php", + rb: "ruby", + go: "go", + rs: "rust", + + // 标记语言 + md: "markdown", + markdown: "markdown", + xml: "xml", + svg: "xml", + yaml: "yaml", + yml: "yaml", + json: "javascript", + + // 其他 + sql: "sql", + sh: "shell", + bash: "shell", + dockerfile: "dockerfile", + docker: "dockerfile", + + // 配置文件 + conf: "properties", + config: "properties", + ini: "properties", + properties: "properties" + } + + return modeMap[extension] || "javascript" // 默认返回 javascript + } + + const options = useMemo(() => { + // 几个推荐的亮色主题: + // idea (IntelliJ IDEA风格) theme: "idea" + // eclipse (Eclipse IDE风格) theme: "eclipse" + // xq-light (清爽的浅色主题) theme: "xq-light" + // solarized (Solarized Light) theme: "solarized" + // neat (简洁主题) theme: "neat" + let setting: any = { + mode: fileName ? getLanguageMode(fileName) : language, + theme: theme, + lineNumbers: true, + viewportMargin: Infinity, // 关键设置 + lineWrapping: false, // 设置为 false 禁止换行 + smartIndent: true, + autoCloseBrackets: true, + matchBrackets: true, + styleActiveLine: true, + lint: true, + tabSize: 2, + firstLineNumber, + // 设置只读模式 + readOnly, + extraKeys: { + "Ctrl-Space": "autocomplete" + }, + } + if (readOnly) { + // 隐藏光标 + setting.cursorHeight = 0 + } + return setting + }, [readOnly, fileName]) + + useEffect(() => { + if (!codemirrorEditor || !highLight) return + // 清除之前的所有高亮 + const marks = codemirrorEditor.getAllMarks() + marks.forEach((mark: any) => mark.clear()) + + // from: {line: 0, ch: 0}, // 开始位置:第1行,第1个字符 + // to: {line: 2, ch: 5} // 结束位置:第3行,第6个字符 + // 由于上诉原因,因此需要对传入数据做处理 + const {from, to} = highLight + const newForm = {line: from.line - 1, ch: from.ch - 1} + const newTo = {line: to.line - 1, ch: to.ch - 1} + // 添加新的高亮 + codemirrorEditor.markText(newForm, newTo, { + className: highLight.className || styles["highlight-text"], + css: "background-color: #fff3b2" // 可以直接设置样式,或者通过 className 在 CSS 中设置 + }) + }, [codemirrorEditor, highLight]) + + return ( +
+ { + onChange && onChange(value) + }} + onChange={(editor, data, value) => { + // 如果需要在onChange时做些什么 后续需要时自行补充 + }} + editorDidMount={(editor) => { + setCodemirrorEditor(editor) + }} + /> +
+ ) +} diff --git a/app/renderer/src/main/src/components/yakitUI/YakitAutoComplete/YakitAutoComplete.module.scss b/app/renderer/src/main/src/components/yakitUI/YakitAutoComplete/YakitAutoComplete.module.scss index 9cf95f897f..a40c4c5eb4 100644 --- a/app/renderer/src/main/src/components/yakitUI/YakitAutoComplete/YakitAutoComplete.module.scss +++ b/app/renderer/src/main/src/components/yakitUI/YakitAutoComplete/YakitAutoComplete.module.scss @@ -38,6 +38,7 @@ border-color: var(--yakit-border-hover-color); } .ant-select-focused .ant-select-selector { + border-radius: 4px; border-color: var(--yakit-border-focus-color) !important; box-shadow: 0 0 0 2px var(--yakit-box-shadow-color) !important; } diff --git a/app/renderer/src/main/src/components/yakitUI/YakitForm/YakitForm.tsx b/app/renderer/src/main/src/components/yakitUI/YakitForm/YakitForm.tsx index 70a1c5016c..ec8e9e228e 100644 --- a/app/renderer/src/main/src/components/yakitUI/YakitForm/YakitForm.tsx +++ b/app/renderer/src/main/src/components/yakitUI/YakitForm/YakitForm.tsx @@ -86,6 +86,8 @@ export const YakitDragger: React.FC = React.memo((props) => { size, inputProps = {}, help = "可将文件拖入框内或点击此处", + uploadFileText, + uploadFolderText, value: fileName, onChange: setFileName, setContent, @@ -487,7 +489,7 @@ export const YakitDragger: React.FC = React.memo((props) => { onUploadFile() }} > - 上传文件 + {uploadFileText||"上传文件"} {isShowPathNumber && ( @@ -514,7 +516,7 @@ export const YakitDragger: React.FC = React.memo((props) => { onUploadFolder() }} > - 上传文件夹 + {uploadFolderText||"上传文件夹"} {isShowPathNumber && ( @@ -541,7 +543,7 @@ export const YakitDragger: React.FC = React.memo((props) => { onUploadFile() }} > - 上传文件 + {uploadFileText||"上传文件"} = React.memo((props) => { onUploadFolder() }} > - 上传文件夹 + {uploadFolderText||"上传文件夹"} diff --git a/app/renderer/src/main/src/components/yakitUI/YakitForm/YakitFormType.d.ts b/app/renderer/src/main/src/components/yakitUI/YakitForm/YakitFormType.d.ts index 7ff826609a..9ff3998521 100644 --- a/app/renderer/src/main/src/components/yakitUI/YakitForm/YakitFormType.d.ts +++ b/app/renderer/src/main/src/components/yakitUI/YakitForm/YakitFormType.d.ts @@ -27,6 +27,8 @@ export interface YakitDraggerProps extends FileDraggerProps { inputProps?: InputProps /**@description selectType为file,该属性才有效*/ setContent?: (s: string) => void + uploadFileText?: string + uploadFolderText?:string help?: ReactDOM showDefHelp?: boolean /**回显的文本值 */ diff --git a/app/renderer/src/main/src/components/yakitUI/YakitTextArea/YakitTextArea.module.scss b/app/renderer/src/main/src/components/yakitUI/YakitTextArea/YakitTextArea.module.scss deleted file mode 100644 index 3c71fe8830..0000000000 --- a/app/renderer/src/main/src/components/yakitUI/YakitTextArea/YakitTextArea.module.scss +++ /dev/null @@ -1,193 +0,0 @@ -.yakit-textArea-images { - display: flex; - gap: 16px; - .upload-img-opt { - cursor: pointer; - position: relative; - display: flex; - :global { - .ant-image { - border-radius: 4px; - overflow: hidden; - img { - height: 100%; - width: 100%; - object-fit: cover; - overflow: hidden; - } - } - } - .opt-pic { - width: 56px; - height: 56px; - } - .mask-box { - display: none; - align-items: center; - justify-content: center; - border-radius: 4px; - position: absolute; - left: 0px; - right: 0px; - top: 0px; - bottom: 0px; - background-color: rgba(0, 0, 0, 0.2); - color: #fff; - font-size: 12px; - font-weight: 400; - line-height: 16px; - } - .close { - display: none; - cursor: pointer; - padding: 4px; - border-radius: 20px; - border: 2px solid #fff; - background: #f0f1f3; - position: absolute; - top: -12px; - right: -12px; - box-sizing: content-box; - height: 12px; - width: 12px; - overflow: hidden; - transition: all 0.2s ease-in-out; - svg { - position: relative; - top: -6px; - left: 0px; - height: 12px; - width: 12px; - color: #85899e; - } - &:hover { - background-color: var(--yakit-primary-5); - svg { - color: #ffffff; - } - } - } - &:hover { - .mask-box { - display: flex; - } - .close { - display: block; - } - } - } -} - -.yakit-textarea { - flex: 1; - border-radius: 4px; - transition: all 0.3s; - border: 1px solid var(--yakit-border-color); - .padding-top-small { - height: 8px; - width: 100%; - } - .padding-top-middle{ - height: 12px; - width: 100%; - } - .padding-left-right { - display: flex; - flex-direction: row; - .input-upload { - flex: 1; - } - } - .padding-left-right-small{ - .padding-left, - .padding-right { - width: 8px; - } - } - .padding-left-right-middle{ - .padding-left, - .padding-right { - width: 12px; - } - } - - .padding-bottom-small { - height: 8px; - width: 100%; - } - - .padding-bottom-middle { - height: 12px; - width: 100%; - } - - .input-box { - position: relative; - .input-box-textArea { - padding: 0px; - max-height: 120px; - } - .input-box-textArea-small{ - font-size: 12px; - } - .textArea-resizer-icon { - position: absolute; - bottom: 0px; - right: 0px; - color: var(--yakit-body-text-color); - z-index: 1; - pointer-events: none; - background-color: #fff; - } - } - .upload-box { - display: flex; - flex-direction: row; - padding-top: 4px; - .upload-box-left { - } - .upload-box-right { - display: flex; - gap: 8px; - align-items: center; - .limit-count { - padding: 2px 6px; - border-radius: 8px; - background: #f0f1f3; - color: #85899e; - text-align: center; - font-size: 10px; - font-style: normal; - font-weight: 400; - line-height: 12px; - } - .disabled-btn { - background: var(--yakit-disable-text-color); - color: var(--ui-text-inverse-color); - border-color: var(--yakit-disable-text-color); - height: 24px; - padding: 3px 8px; - font-weight: 500; - font-size: 12px; - display: flex; - justify-content: center; - align-items: center; - gap: 4px; - box-shadow: none; - border-radius: 4px; - border: 1px solid transparent; - cursor: not-allowed; - } - } - } - .upload-box-text { - justify-content: flex-end; - } - .upload-box-images { - justify-content: space-between; - } -} -.yakit-textarea-active { - border-color: var(--yakit-primary-4); - box-shadow: 0 0 0 2px var(--yakit-primary-2); -} diff --git a/app/renderer/src/main/src/components/yakitUI/YakitTextArea/YakitTextArea.tsx b/app/renderer/src/main/src/components/yakitUI/YakitTextArea/YakitTextArea.tsx deleted file mode 100644 index ca743abf28..0000000000 --- a/app/renderer/src/main/src/components/yakitUI/YakitTextArea/YakitTextArea.tsx +++ /dev/null @@ -1,401 +0,0 @@ -import React, {useEffect, useMemo, useRef, useState} from "react" -import {Input, InputRef, Image, Upload} from "antd" -import {LoadingOutlined} from "@ant-design/icons" -import styles from "./YakitTextArea.module.scss" -import classNames from "classnames" -import cloneDeep from "lodash/cloneDeep" -import {YakitButton} from "../YakitButton/YakitButton" -import {PaperAirplaneIcon, ResizerIcon} from "@/assets/newIcon" -import {CloseIcon} from "@/components/configNetwork/icon" -import {failed} from "@/utils/notification" -import {OutlinePhotographIcon} from "@/assets/icon/outline" -import {TextAreaProps} from "antd/lib/input" -const {ipcRenderer} = window.require("electron") - -interface ImageShowProps { - src: string - visible: boolean -} - -/** - * @description YakitTextAreaImagesProps 的属性 - * @param {string} className - * @param {string[]} images 显示的图片数据 - * @param {(v: string[]) => void} onDelete 删除图片后剩余图片数据的回调 - * @param {() => void} onOpen 放大图片的回调 - * @param {() => void} onClose 关闭放大图片的回调 - * @param {string | number} key 辅助key值 - * @param {number} size 展示图片大小 默认56 - */ - -interface YakitTextAreaImagesProps { - className?: string - images: string[] - onDelete?: (v: string[]) => void - onOpen?: () => void - onClose?: () => void - key?: string | number - size?: number -} - -export const YakitTextAreaImages: React.FC = (props) => { - const {className, images, onDelete, onOpen, onClose, key, size = 56} = props - const [imageShow, setImageShow] = useState({ - src: "", - visible: false - }) - return ( -
- {images.map((item, index) => { - return ( -
{ - e.stopPropagation() - }} - style={{width: size, height: size}} - > - -
{ - onOpen && onOpen() - setImageShow({ - visible: true, - src: item - }) - }} - > - 预览 -
- {onDelete && ( -
{ - e.stopPropagation() - const arr: string[] = cloneDeep(images) - arr.splice(index, 1) - onDelete && onDelete(arr) - }} - > - -
- )} -
- ) - })} - { - if (!value) { - onClose && onClose() - setImageShow({ - visible: false, - src: "" - }) - } - } - }} - /> -
- ) -} -/** - * @description YakitTextAreaProps 的属性 - * @augments TextAreaProps 继承antd的TextAreaProps默认属性 - * @param {string} type 类型 默认文本域(text文本域、images图片文本域) - * @param {"small" | "middle"} textAreaSize 控件尺寸 - * @param {string} wrapClassName - * @param {string} value 输入框值 - * @param {(value:string) => void} setValue 更改输入框值 - * @param {boolean} isLimit 是否限制输入字数 默认限制 - * @param {number} limit 限制输入字数,默认150字 - * @param {boolean} loading 是否加载中 - * @param {string[]} files 显示图片列表 - * @param {(files: string[]) => void} setFiles 更改显示图片列表 - * @param {() => void} onSubmit 确认回调 - * @param {string} submitTxt 确认按钮文案 - * @param {number} rows TextArea行数 默认4行 - * @param {boolean} isAlwaysShow 是否常驻显示操作按钮(默认为聚焦显示) - */ - -export interface YakitTextAreaProps extends TextAreaProps { - type?: "text" | "images" - textAreaSize?: "small" | "middle" - wrapClassName?: string - value: string - setValue: (value: string) => void - isLimit?: boolean - limit?: number - loading?: boolean - files?: string[] - setFiles?: (files: string[]) => void - onSubmit?: () => void - submitTxt?: string - rows?: number - isAlwaysShow?: boolean - // 是否不允许提交 - noSubmit?: boolean -} -export const YakitTextArea: React.FC = (props) => { - const { - type = "text", - textAreaSize = "middle", - wrapClassName, - value, - setValue, - isLimit = true, - limit = 150, - loading, - files = [], - setFiles, - onSubmit, - submitTxt = "发布评论", - rows = 4, - isAlwaysShow, - noSubmit = false, - ...resProps - } = props - const [filesLoading, setFilesLoading] = useState(false) - const textAreRef = useRef(null) - const [isFocused, setIsFocused] = useState(false) - // 是否允许其失焦 - const isAllowBlur = useRef(true) - const uploadFiles = (file) => { - setFilesLoading(true) - ipcRenderer - .invoke("http-upload-img-path", {path: file.path}) - .then((res) => { - setFiles && setFiles(files.concat(res.data)) - }) - .catch((err) => { - fail("图片上传失败") - }) - .finally(() => { - setTimeout(() => setFilesLoading(false), 1) - }) - } - - const disabled = useMemo(() => { - if (value.length > limit && isLimit) { - return true - } - if (value.length === 0 && files.length === 0) { - return true - } - return false - }, [value, limit, files, isLimit]) - - // 是否展示底部元素 - const isShowdDom = useMemo(() => { - if (files.length > 0 || value.length > 0 || isAlwaysShow || isFocused) { - return true - } else { - return false - } - }, [isFocused, files, value]) - return ( -
{ - textAreRef.current!.focus({ - cursor: "end" - }) - }} - > - {/* 将原本padding: 12px;拆分为4个div的原因是为了解决控件闪烁 */} -
{ - e.preventDefault() - }} - >
-
-
{ - e.preventDefault() - }} - >
-
-
{ - e.stopPropagation() - }} - > - { - setIsFocused(true) - }} - onBlur={() => { - if (isAllowBlur.current) { - setIsFocused(false) - } else { - isAllowBlur.current = true - textAreRef.current?.focus() - } - }} - value={value} - ref={textAreRef} - bordered={false} - rows={rows} - onChange={(e) => setValue(e.target.value)} - /> - -
- {isShowdDom && type === "images" && ( -
{ - e.preventDefault() - }} - > - { - isAllowBlur.current = false - }} - onClose={() => { - isAllowBlur.current = true - }} - /> -
- )} - {isShowdDom && type === "text" && ( -
{ - e.preventDefault() - }} - >
- )} - {isShowdDom && ( -
{ - e.preventDefault() - }} - > - {type === "images" && ( -
{ - isAllowBlur.current = false - }} - > - = 3} - showUploadList={false} - beforeUpload={(file: any) => { - if (filesLoading) { - return false - } - if (file.size / 1024 / 1024 > 10) { - failed("图片大小不超过10M") - return false - } - if (!"image/jpeg,image/png,image/jpg,image/gif".includes(file.type)) { - failed("仅支持上传图片格式为:image/jpeg,image/png,image/jpg,image/gif") - return false - } - if (file) { - uploadFiles(file) - } - return true - }} - > - {filesLoading ? ( - - ) : ( - = 3} - icon={} - type='text2' - /> - )} - -
- )} -
- {isLimit && ( -
- {value.length}/{limit} -
- )} - <> - { - // 由于Button的disabled为true时会被阻止默认事件向上传递 因此disable样式由自己实现 - disabled ? ( -
- - {submitTxt} -
- ) : ( - } - loading={loading} - onClick={() => { - onSubmit && onSubmit() - }} - disabled={noSubmit} - > - {submitTxt || "确认"} - - ) - } - -
-
- )} -
-
{ - e.preventDefault() - }} - >
-
- -
{ - e.preventDefault() - }} - >
-
- ) -} diff --git a/app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualList.module.scss b/app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualList.module.scss new file mode 100644 index 0000000000..dd68a2ba11 --- /dev/null +++ b/app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualList.module.scss @@ -0,0 +1,160 @@ +.text-center { + text-align: center; +} + +%display-flex-space-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +%display-flex-center { + display: flex; + align-items: center; +} +.no-more-text { + font-size: 12px; + color: var(--yakit-helper-text-color); + height: 28px; + display: flex; + align-items: center; + justify-content: center; +} +.virtual-list { + display: flex; + flex-direction: column; + flex: 1; + .virtual-list-columns { + @extend %display-flex-space-between; + height: 32px; + padding-left: 8px; + + color: var(--yakit-header-color); + font-size: 12px; + font-weight: 600; + line-height: 16px; + gap: 8px; + + border-bottom: 1px solid var(--yakit-border-color); + .columns-item { + align-items: center; + display: flex; + gap: 8px; + overflow: hidden; + } + .columns-item-flex { + flex: 1; + } + } + .virtual-list-columns-scroll { + padding-right: 8px; //占滚动条位置 + } + .virtual-list-content { + flex: 1; + overflow: hidden; + .virtual-list-container { + overflow-y: overlay; + overflow-anchor: none; + } + .virtual-list-wrapper { + .virtual-list-item { + @extend %display-flex-space-between; + padding-left: 8px; + height: 48px; + border-top: 1px solid var(--yakit-border-color); + + color: var(--yakit-header-color); + font-size: 12px; + font-weight: 400; + line-height: 16px; + gap: 8px; + cursor: pointer; + + &:hover { + border-top-color: transparent; + background: var(--yakit-card-background-color); + border-radius: 8px; + } + &:first-child { + border-top-width: 0; + } + .virtual-list-cell { + align-items: center; + display: flex; + gap: 8px; + overflow: hidden; + } + .virtual-list-cell-flex { + flex: 1; + } + } + .virtual-list-item:hover + .virtual-list-item { + border-top-color: transparent; + } + } + } +} + +.author-filter-popover { + padding-top: 0; + :global { + .ant-popover-arrow { + display: none; + } + .ant-popover-inner-content { + padding: 8px 0; + } + } +} + +.filter-popover-content { + display: flex; + flex-direction: column; + width: 216px; + .search-icon { + svg { + width: 16px; + height: 16px; + color: var(--yakit-body-text-color); + } + } + .search-wrapper { + padding: 6px 12px 8px; + } + .option-list-content { + flex: 1; + .option-list-container { + min-height: 28px; + max-height: 50vh; + overflow-y: overlay; + } + .option-list-wrapper { + display: flex; + flex-direction: column; + padding: 0 4px; + .option-item { + @extend %display-flex-center; + gap: 12px; + overflow: hidden; + cursor: pointer; + padding: 0 8px; + border-radius: 4px; + height: 28px; + &:hover { + background-color: #eef0f3; + } + .item-label { + @extend %display-flex-center; + gap: 4px; + overflow: hidden; + } + } + } + } + .option-list-footer { + display: flex; + justify-content: space-between; + padding: 8px 12px 0 4px; + border-top: 1px solid var(--yakit-border-color); + } +} diff --git a/app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualList.tsx b/app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualList.tsx new file mode 100644 index 0000000000..efc93973b7 --- /dev/null +++ b/app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualList.tsx @@ -0,0 +1,384 @@ +import classNames from "classnames" +import {ListSelectFilterPopoverProps, YakitVirtualListProps} from "./YakitVirtualListType" +import styles from "./YakitVirtualList.module.scss" +import { + useControllableValue, + useCreation, + useDebounceEffect, + useMemoizedFn, + useThrottleFn, + useUpdateEffect, + useVirtualList +} from "ahooks" +import {useRef, useState} from "react" +import ReactResizeDetector from "react-resize-detector" +import {LoadingOutlined} from "@ant-design/icons" +import { + YakitProtoCheckbox, + YakitProtoCheckboxProps +} from "@/components/TableVirtualResize/YakitProtoCheckbox/YakitProtoCheckbox" +import React from "react" +import {YakitPopover} from "@/components/yakitUI/YakitPopover/YakitPopover" +import {YakitInput} from "@/components/yakitUI/YakitInput/YakitInput" +import {AuthorImg} from "@/pages/plugins/funcTemplate" +import {OutlineSearchIcon} from "@/assets/icon/outline" +import {YakitEmpty} from "@/components/yakitUI/YakitEmpty/YakitEmpty" +import SearchResultEmpty from "@/assets/search_result_empty.png" +import {YakitButton} from "@/components/yakitUI/YakitButton/YakitButton" +import {YakitSpin} from "@/components/yakitUI/YakitSpin/YakitSpin" + +export const YakitVirtualList = (props: YakitVirtualListProps) => { + const { + className, + columns, + data, + loading, + hasMore = true, + refresh, + renderKey = "id", + rowSelection, + page = 0, + loadMoreData + } = props + + const [vlistHeigth, setVListHeight] = useState(600) + const [scroll, setScroll] = useState(false) + + const containerRef = useRef(null) + const wrapperRef = useRef(null) + + const [list, scrollTo] = useVirtualList(data, { + containerTarget: containerRef, + wrapperTarget: wrapperRef, + itemHeight: 48, + overscan: 5 + }) + + useDebounceEffect( + () => { + if (!hasMore) return + if (!containerRef || !wrapperRef) return + // wrapperRef 中的数据没有铺满 containerRef,那么就要请求更多的数据 + const containerHeight = containerRef.current?.clientHeight || 0 + const wrapperHeight = wrapperRef.current?.clientHeight + // console.log("wrapperHeight", wrapperHeight, containerHeight) + if (wrapperHeight && wrapperHeight <= containerHeight) { + loadMoreData() + setScroll(false) + } else { + setScroll(true) + } + }, + [vlistHeigth, wrapperRef.current?.clientHeight, hasMore], + {wait: 200} + ) + + useUpdateEffect(() => { + scrollTo(0) + }, [refresh]) + + const onScrollCapture = useThrottleFn( + () => { + if (wrapperRef && containerRef && !loading && hasMore) { + const dom = containerRef.current || { + scrollTop: 0, + clientHeight: 0, + scrollHeight: 0 + } + const contentScrollTop = dom.scrollTop //滚动条距离顶部 + const clientHeight = dom.clientHeight //可视区域 + const scrollHeight = dom.scrollHeight //滚动条内容的总高度 + const scrollBottom = scrollHeight - contentScrollTop - clientHeight + if (scrollBottom <= 500) { + loadMoreData() // 获取数据的方法 + } + } + }, + {wait: 200, leading: false} + ).run + const onChangeCheckboxSingle = useMemoizedFn((checked: boolean, key: string, row: T) => { + if (!rowSelection) return + if (!rowSelection.onChangeCheckboxSingle) return + rowSelection.onChangeCheckboxSingle(checked, key, row) + }) + const onChangeCheckbox = useMemoizedFn((checked: boolean) => { + if (!rowSelection) return + if (!rowSelection.onSelectAll) return + if (checked) { + const keys = data.map((ele, index) => (renderKey ? ele[renderKey] : index)) + rowSelection.onSelectAll(keys, data, checked) + } else { + rowSelection.onSelectAll([], [], checked) + } + }) + const checkboxPropsMap = useCreation(() => { + const map = new Map>() + const {getCheckboxProps} = rowSelection || {} + if (!!getCheckboxProps) { + data.forEach((record, index) => { + const key = record[renderKey] + const checkboxProps = getCheckboxProps(record) || {} + map.set(key, checkboxProps) + }) + } + return map + }, [data, rowSelection?.getCheckboxProps]) + const isAll = useCreation(() => { + return rowSelection?.isAll || (list.length > 0 && rowSelection?.selectedRowKeys?.length === data.length) + }, [rowSelection?.isAll, list.length, data.length, rowSelection?.selectedRowKeys?.length]) + return ( +
+ +
+ {columns.map((columnsItem, columnsIndex) => { + return ( +
+ {columnsIndex === 0 && rowSelection && ( + 0} + onChange={(e) => { + onChangeCheckbox(e.target.checked) + }} + /> + )} + {columnsItem.filterProps?.filterRender ? ( + columnsItem.filterProps?.filterRender() + ) : ( +
{columnsItem.title}
+ )} +
+ ) + })} +
+
+ { + if (!height) { + return + } + setVListHeight(height) + }} + handleWidth={true} + handleHeight={true} + refreshMode={"debounce"} + refreshRate={50} + /> +
+
+ {list.map((ele) => ( +
+ {columns.map((item, index) => { + return ( +
+ {index === 0 && rowSelection && ( + { + onChangeCheckboxSingle( + e.target.checked, + renderKey ? ele.data[renderKey] : ele.index, + ele.data + ) + }} + checked={ + rowSelection?.selectedRowKeys?.findIndex( + (c) => + c === (renderKey ? ele.data[renderKey] : ele.index) + ) !== -1 + } + {...(checkboxPropsMap.get(ele.data[renderKey]) || {})} + /> + )} + {item?.render ? ( + item.render(ele.data[item.dataIndex], ele.data, ele.index) + ) : ( +
{ele.data[item.dataIndex]}
+ )} +
+ ) + })} +
+ ))} + {loading && hasMore && ( +
+ +
+ )} + {!loading && !hasMore &&
暂无更多数据
} +
+
+
+
+
+ ) +} + +/** + * @description 未经过测试 + * EG: + + + 作者 + + + + */ +export const ListSelectFilterPopover: React.FC = React.memo((props) => { + const {children, option = [], placement, filterOption, onSetValue} = props + const [selectKeys, setSelectKeys] = useControllableValue(props, { + defaultValue: [] + }) + + const [visible, setVisible] = useState(false) + + const [data, setData] = useState(option) + const [searchText, setSearchText] = useState("") + const containerRef = useRef(null) + const wrapperRef = useRef(null) + + useDebounceEffect( + () => { + if (!filterOption) return + const newData = option.filter((optionItem) => { + // 如果 filterOption 是函数,调用函数进行自定义过滤 + if (typeof filterOption === "function") { + return filterOption(searchText, optionItem) + } else if (filterOption === true) { + if (typeof optionItem?.label === "string") { + return optionItem.label?.toUpperCase().indexOf(searchText.toUpperCase()) !== -1 + } + return false + } + return true + }) + setData(newData) + }, + [searchText], + {wait: 200, leading: true} + ) + + const [list] = useVirtualList(data, { + containerTarget: containerRef, + wrapperTarget: wrapperRef, + itemHeight: 28, + overscan: 5 + }) + + const onSelect = useMemoizedFn((item) => { + const checked = selectKeys.includes(item.value) + onCheck(checked, item) + }) + const onCheck = useMemoizedFn((checked, item) => { + if (checked) { + setSelectKeys((prev) => prev.filter((c) => c !== item.value)) + } else { + setSelectKeys((v) => [...v, item.value]) + } + }) + const onClear = useMemoizedFn(() => { + setSelectKeys([]) + }) + const onSave = useMemoizedFn(() => { + onSetValue(selectKeys) + setVisible(false) + }) + const onVisibleChange = useMemoizedFn((v) => { + if (!v) { + onSetValue(selectKeys) + } + setVisible(v) + }) + return ( + +
+ } + value={searchText} + onChange={(e) => setSearchText(e.target.value)} + /> +
+ {option.length > 0 && list.length === 0 && ( + + )} + {option.length === 0 && } +
+
+
+ {list.map((ele, index) => { + const item = ele.data + const checked = selectKeys.includes(item.value) + return ( +
onSelect(item)} + > + onCheck(checked, item)} + /> +
+ {item.heardImgSrc && } + {item.label} +
+
+ ) + })} +
+
+
+
+ + 清空 + + + 确定 + +
+ + } + overlayClassName={styles["author-filter-popover"]} + > + {children} +
+ ) +}) diff --git a/app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualListType.d.ts b/app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualListType.d.ts new file mode 100644 index 0000000000..3e26911db2 --- /dev/null +++ b/app/renderer/src/main/src/components/yakitUI/YakitVirtualList/YakitVirtualListType.d.ts @@ -0,0 +1,52 @@ +import {ReactNode} from "react" +import {FiltersItemProps, RowSelectionProps} from "../../../components/TableVirtualResize/TableVirtualResizeType" +import {YakitSelectProps} from "@/components/yakitUI/YakitSelect/YakitSelectType" +import {YakitPopoverProp} from "@/components/yakitUI/YakitPopover/YakitPopover" + +export interface YakitVirtualListProps { + columns: VirtualListColumns[] + data: T[] + className?: string + hasMore?: boolean + loading?: boolean + /**刷新列表 */ + refresh?: boolean + renderKey?: string + rowSelection?: RowSelectionProps + /**当前页数,如果小于等于1,会显示大的包裹loading */ + page?:number + /**加载下一页 */ + loadMoreData: () => void +} + +export interface VirtualListColumns { + title: ReactNode + dataIndex: string + width?: number + render?: (text, record:T, index) => ReactNode + filterProps?: VirtualListFilterProps + /** 表头Columns单个数据样式 */ + columnsClassName?: string +} + +interface VirtualListFilterProps { + filterRender?: () => ReactNode + filterKey?: string + /**表头的筛选菜单项 */ + filters?: FiltersItemProps[] +} + +export interface ListSelectFilterPopoverProps { + option: ListSelectOptionProps[] + children?: ReactNode + selectKeys: string[] + onSetValue: (s: string[]) => void + placement?: YakitPopoverProp["placement"] + filterOption?: ((inputValue: string, optionItem: ListSelectOptionProps) => boolean) | boolean +} + +export interface ListSelectOptionProps { + value: string + label: ReactNode + heardImgSrc?: string +} diff --git a/app/renderer/src/main/src/constants/software.ts b/app/renderer/src/main/src/constants/software.ts new file mode 100644 index 0000000000..25dc0dfee1 --- /dev/null +++ b/app/renderer/src/main/src/constants/software.ts @@ -0,0 +1,12 @@ +import {YaklangEngineMode} from "@/yakitGVDefine" + +/** Yakit链接模式 远程/本地 */ +let EngineMode: YaklangEngineMode | undefined = undefined + +export const setYakitEngineMode = (v?: YaklangEngineMode) => { + EngineMode = v +} + +export const getYakitEngineMode = () => { + return EngineMode +} \ No newline at end of file diff --git a/app/renderer/src/main/src/enums/yakitRoute.ts b/app/renderer/src/main/src/enums/yakitRoute.ts index a16b812bcf..fdfc880f12 100644 --- a/app/renderer/src/main/src/enums/yakitRoute.ts +++ b/app/renderer/src/main/src/enums/yakitRoute.ts @@ -79,5 +79,7 @@ export enum YakitRoute { // YakRunner代码扫描 YakRunner_Code_Scan = "yakrunner-code-scan", // YakRunner代码审计 - YakRunner_Audit_Code = "yakrunner-audit-code" + YakRunner_Audit_Code = "yakrunner-audit-code", + // YakRunner项目管理 + YakRunner_Project_Manager = "yakrunner-project-manager" } diff --git a/app/renderer/src/main/src/pages/SetPassword.tsx b/app/renderer/src/main/src/pages/SetPassword.tsx index d831d96041..efd814cb73 100644 --- a/app/renderer/src/main/src/pages/SetPassword.tsx +++ b/app/renderer/src/main/src/pages/SetPassword.tsx @@ -2,7 +2,6 @@ import React, {ReactNode, useEffect, useRef, useState} from "react" import {Form, Input, Button} from "antd" import {warn,failed, success} from "@/utils/notification" import {useDebounceFn, useMemoizedFn} from "ahooks" -import {} from "@ant-design/icons" import {NetWorkApi} from "@/services/fetch" import {API} from "@/services/swagger/resposeType" import {loginOut, refreshToken} from "@/utils/login" diff --git a/app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManager.module.scss b/app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManager.module.scss new file mode 100644 index 0000000000..8dcf47d6ed --- /dev/null +++ b/app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManager.module.scss @@ -0,0 +1,13 @@ +.yakrunner-project-manager{ + height: 100%; + overflow: hidden; + position: relative; + :global { + .ant-modal-mask { + position: absolute; + } + .ant-modal-wrap { + position: absolute; + } + } +} \ No newline at end of file diff --git a/app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManager.tsx b/app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManager.tsx new file mode 100644 index 0000000000..62955c89d0 --- /dev/null +++ b/app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManager.tsx @@ -0,0 +1,58 @@ +import React, {memo, useEffect, useMemo, useRef, useState} from "react" +import {YakRunnerProjectManagerProps} from "./YakRunnerProjectManagerType" +import {useGetState, useMemoizedFn} from "ahooks" +import {NetWorkApi} from "@/services/fetch" +import {API} from "@/services/swagger/resposeType" +import styles from "./YakRunnerProjectManager.module.scss" +import {failed, success, warn, info} from "@/utils/notification" +import classNames from "classnames" +import emiter from "@/utils/eventBus/eventBus" +import {YakitButton} from "@/components/yakitUI/YakitButton/YakitButton" +import {YakitInput} from "@/components/yakitUI/YakitInput/YakitInput" +import {OutlineSearchIcon} from "@/assets/icon/outline" +import {AuditHistoryTable, AuditModalFormModal} from "../yakRunnerAuditCode/AuditCode/AuditCode" +import {isCommunityEdition} from "@/utils/envfile" +import {WaterMark} from "@ant-design/pro-layout" +const {ipcRenderer} = window.require("electron") + +export const YakRunnerProjectManager: React.FC = (props) => { + const [isShowCompileModal, setShowCompileModal] = useState(false) + const [refresh, setRefresh] = useState(false) + const waterMarkStr = useMemo(() => { + if (isCommunityEdition()) { + return "Yakit技术浏览版仅供技术交流使用" + } + return " " + }, []) + + const onCloseCompileModal = useMemoizedFn(() => { + setShowCompileModal(false) + }) + + const onSuccee = () => { + onCloseCompileModal() + setRefresh(!refresh) + } + + return ( + +
+ { + setShowCompileModal(true) + }} + refresh={refresh} + setRefresh={setRefresh} + /> + {isShowCompileModal && ( + + )} +
+
+ ) +} diff --git a/app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManagerType.d.ts b/app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManagerType.d.ts new file mode 100644 index 0000000000..1df6e3ede8 --- /dev/null +++ b/app/renderer/src/main/src/pages/YakRunnerProjectManager/YakRunnerProjectManagerType.d.ts @@ -0,0 +1 @@ +export interface YakRunnerProjectManagerProps {} \ No newline at end of file diff --git a/app/renderer/src/main/src/pages/invoker/YakitLogFormatter.tsx b/app/renderer/src/main/src/pages/invoker/YakitLogFormatter.tsx index 245cc800c8..4fa811808b 100644 --- a/app/renderer/src/main/src/pages/invoker/YakitLogFormatter.tsx +++ b/app/renderer/src/main/src/pages/invoker/YakitLogFormatter.tsx @@ -165,7 +165,7 @@ export const YakitLogFormatter: React.FC = React.memo((pr
{showTime && {formatTime(timestamp)}} · - {data} + {data}
) } diff --git a/app/renderer/src/main/src/pages/layout/publicMenu/MenuMode.tsx b/app/renderer/src/main/src/pages/layout/publicMenu/MenuMode.tsx index 1fbe58313c..611147e685 100644 --- a/app/renderer/src/main/src/pages/layout/publicMenu/MenuMode.tsx +++ b/app/renderer/src/main/src/pages/layout/publicMenu/MenuMode.tsx @@ -18,6 +18,7 @@ import { PublicPluginStoreIcon, PublicPocIcon, PublicPortsIcon, + PublicProjectManagerIcon, PublicReportIcon, PublicReverseServerIcon, PublicRiskIcon, @@ -342,6 +343,18 @@ export const MenuMode: React.FC = React.memo((props) => { )} {mode === "代码审计" && ( <> +
onMenu(YakitRoute.YakRunner_Project_Manager)} + > +
+
+ +
+
+
项目管理
+
+
onMenu(YakitRoute.YakRunner_Audit_Code)} diff --git a/app/renderer/src/main/src/pages/mitm/MITMYakScriptLoader.tsx b/app/renderer/src/main/src/pages/mitm/MITMYakScriptLoader.tsx index e2e3785aa5..621c7f8b8b 100644 --- a/app/renderer/src/main/src/pages/mitm/MITMYakScriptLoader.tsx +++ b/app/renderer/src/main/src/pages/mitm/MITMYakScriptLoader.tsx @@ -34,6 +34,7 @@ import {SolidDotsverticalIcon, SolidLightningboltIcon} from "@/assets/icon/solid import {YakitMenuItemProps} from "@/components/yakitUI/YakitMenu/YakitMenu" import {YakitPopover} from "@/components/yakitUI/YakitPopover/YakitPopover" import {YakEditor} from "@/utils/editors" +import { getJsonSchemaListResult } from "@/components/JsonFormWrapper/JsonFormWrapper" const {Text} = Typography const {ipcRenderer} = window.require("electron") @@ -466,6 +467,9 @@ interface MitmHasParamsFormProps { const MitmHasParamsForm = React.forwardRef((props: MitmHasParamsFormProps, ref) => { const {initFormValue, requiredParams, groupParams} = props const [form] = Form.useForm() + const jsonSchemaListRef = useRef<{ + [key: string]: any; + }>({}) useImperativeHandle( ref, @@ -480,6 +484,14 @@ const MitmHasParamsForm = React.forwardRef((props: MitmHasParamsFormProps, ref) if (!form) return resolve(undefined) form.validateFields() .then((values) => { + const result = getJsonSchemaListResult(jsonSchemaListRef.current) + if(result.jsonSchemaError.length>0) { + failed(`jsonSchema校验失败`) + return + } + result.jsonSchemaSuccess.forEach((item)=>{ + values[item.key] = JSON.stringify(item.value) + }) resolve(values) }) .catch(() => { @@ -496,7 +508,7 @@ const MitmHasParamsForm = React.forwardRef((props: MitmHasParamsFormProps, ref) wrapperCol={{span: 15}} initialValues={initFormValue} > - + ) diff --git a/app/renderer/src/main/src/pages/payloadManager/newPayloadTable.tsx b/app/renderer/src/main/src/pages/payloadManager/newPayloadTable.tsx index 05eb241510..da140db41a 100644 --- a/app/renderer/src/main/src/pages/payloadManager/newPayloadTable.tsx +++ b/app/renderer/src/main/src/pages/payloadManager/newPayloadTable.tsx @@ -1,5 +1,4 @@ import React, {useContext, useEffect, useMemo, useRef, useState} from "react" -import {} from "@ant-design/icons" import {useDebounceFn, useGetState, useMemoizedFn} from "ahooks" import {NetWorkApi} from "@/services/fetch" import {API} from "@/services/swagger/resposeType" diff --git a/app/renderer/src/main/src/pages/pluginEditor/editorCode/EditorCode.tsx b/app/renderer/src/main/src/pages/pluginEditor/editorCode/EditorCode.tsx index 09c9853ffc..a99c4f835c 100644 --- a/app/renderer/src/main/src/pages/pluginEditor/editorCode/EditorCode.tsx +++ b/app/renderer/src/main/src/pages/pluginEditor/editorCode/EditorCode.tsx @@ -30,7 +30,7 @@ import { } from "@/pages/plugins/operator/localPluginExecuteDetailHeard/PluginExecuteExtraParams" import useHoldGRPCStream from "@/hook/useHoldGRPCStream/useHoldGRPCStream" import {randomString} from "@/utils/randomUtil" -import {yakitNotify} from "@/utils/notification" +import {failed, warn, yakitNotify} from "@/utils/notification" import {DebugPluginRequest, apiCancelDebugPlugin, apiDebugPlugin} from "@/pages/plugins/utils" import {HTTPRequestBuilderParams} from "@/models/HTTPRequestBuilder" import emiter from "@/utils/eventBus/eventBus" @@ -44,6 +44,7 @@ import {CodeScoreModal} from "@/pages/plugins/funcTemplate" import classNames from "classnames" import "../../plugins/plugins.scss" import styles from "./EditorCode.module.scss" +import { getJsonSchemaListResult, JsonFormValidateProps } from "@/components/JsonFormWrapper/JsonFormWrapper" export interface EditorCodeRefProps { onSubmit: () => string @@ -143,6 +144,9 @@ export const EditorCode: React.FC = memo( /** ---------- 参数获取和展示逻辑 Start ---------- */ const [form] = Form.useForm() + const jsonSchemaListRef = useRef<{ + [key: string]: any; + }>({}) // 设置非(yak|lua)类型的插件参数初始值 const onSettingDefault = useMemoizedFn(() => { @@ -183,7 +187,7 @@ export const EditorCode: React.FC = memo( form.setFieldsValue({...cloneDeep(defaultValue || {}), ...newFormValue}) }) - const pluginRequiredItem = (type: string) => { + const pluginRequiredItem = useMemoizedFn((type: string) => { switch (type) { case "yak": case "lua": @@ -192,6 +196,7 @@ export const EditorCode: React.FC = memo( paramsList={requiredParams} pluginType={type} isExecuting={isExecuting} + jsonSchemaListRef={jsonSchemaListRef} /> ) case "codec": @@ -219,6 +224,7 @@ export const EditorCode: React.FC = memo( paramsList={requiredParams} pluginType={type} isExecuting={isExecuting} + jsonSchemaListRef={jsonSchemaListRef} /> ) : null} @@ -230,7 +236,7 @@ export const EditorCode: React.FC = memo( default: return null } - } + }) /** 请求类型-为原始请求则不展示额外参数 */ const requestType = Form.useWatch("requestType", form) @@ -264,7 +270,7 @@ export const EditorCode: React.FC = memo(
额外参数 (非必填)
- +
已经到底啦~
@@ -279,7 +285,7 @@ export const EditorCode: React.FC = memo(
自定义参数 (非必填)
- + ) : null} {!isHiddenDefaultParams && ( @@ -347,6 +353,16 @@ export const EditorCode: React.FC = memo( if (form) { form.validateFields() .then(async (value: any) => { + + const result = getJsonSchemaListResult(jsonSchemaListRef.current) + if(result.jsonSchemaError.length>0) { + failed(`jsonSchema校验失败`) + return + } + result.jsonSchemaSuccess.forEach((item)=>{ + value[item.key] = JSON.stringify(item.value) + }) + // 保存参数-请求路径的选项 if (pathRef && pathRef.current) { pathRef.current.onSetRemoteValues(value?.Path || []) diff --git a/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard.module.scss b/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard.module.scss index 41a12829f4..66242e21a1 100644 --- a/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard.module.scss +++ b/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard.module.scss @@ -103,4 +103,4 @@ .expand-retract-animation-wrapper { top: 85%; } -} +} \ No newline at end of file diff --git a/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard.tsx b/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard.tsx index 30854836ac..e8687a89b6 100644 --- a/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard.tsx +++ b/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo, useRef, useState} from "react" +import React, {ChangeEvent, useEffect, useMemo, useRef, useState} from "react" import { ExecuteEnterNodeByPluginParamsProps, FormExtraSettingProps, @@ -10,12 +10,12 @@ import { YakExtraParamProps, FormContentItemByTypeProps, PluginFixFormParamsProps, - RequestType + RequestType, } from "./LocalPluginExecuteDetailHeardType" import {PluginDetailHeader} from "../../baseTemplate" import styles from "./LocalPluginExecuteDetailHeard.module.scss" import {useCreation, useDebounceFn, useInViewport, useMemoizedFn, useNetwork} from "ahooks" -import {Divider, Form, Progress} from "antd" +import {Divider, Form, Input, Progress} from "antd" import {PluginParamDataEditorProps, YakParamProps} from "../../pluginsType" import {YakitInput} from "@/components/yakitUI/YakitInput/YakitInput" import {YakitInputNumber} from "@/components/yakitUI/YakitInputNumber/YakitInputNumber" @@ -26,7 +26,7 @@ import { YakitFormDraggerContent, YakitFormDraggerContentPath } from "@/components/yakitUI/YakitForm/YakitForm" -import {failed} from "@/utils/notification" +import {failed, warn} from "@/utils/notification" import {YakitButton} from "@/components/yakitUI/YakitButton/YakitButton" import classNames from "classnames" import {YakitSelect} from "@/components/yakitUI/YakitSelect/YakitSelect" @@ -43,6 +43,7 @@ import {ExpandAndRetract} from "../expandAndRetract/ExpandAndRetract" import {defPluginExecuteFormValue} from "./constants" import {YakitAutoComplete} from "@/components/yakitUI/YakitAutoComplete/YakitAutoComplete" import {grpcFetchExpressionToResult} from "@/pages/pluginHub/utils/grpc" +import { getJsonSchemaListResult, JsonFormWrapper } from "@/components/JsonFormWrapper/JsonFormWrapper" const PluginExecuteExtraParams = React.lazy(() => import("./PluginExecuteExtraParams")) @@ -83,6 +84,10 @@ export const LocalPluginExecuteDetailHeard: React.FC() const localPluginExecuteDetailHeardRef = useRef(null) + const jsonSchemaListRef = useRef<{ + [key: string]: any; + }>({}) + const [inViewport = true] = useInViewport(localPluginExecuteDetailHeardRef) const networkState = useNetwork() @@ -165,7 +170,7 @@ export const LocalPluginExecuteDetailHeard: React.FC { + const pluginParamsNodeByPluginType = useMemoizedFn((type: string) => { switch (type) { case "yak": case "lua": @@ -174,6 +179,7 @@ export const LocalPluginExecuteDetailHeard: React.FC ) case "codec": @@ -201,6 +207,7 @@ export const LocalPluginExecuteDetailHeard: React.FC ) : null} @@ -212,12 +219,24 @@ export const LocalPluginExecuteDetailHeard: React.FC } - } + }) /**开始执行 */ const onStartExecute = useMemoizedFn((value) => { let yakExecutorParams: YakExecutorParam[] = [] yakExecutorParams = getYakExecutorParam({...value, ...customExtraParamsValue}) const input = value["Input"] + const result = getJsonSchemaListResult(jsonSchemaListRef.current) + + if(result.jsonSchemaError.length>0) { + failed(`jsonSchema校验失败`) + return + } + result.jsonSchemaSuccess.forEach((item)=>{ + yakExecutorParams.push({ + Key:item.key, + Value:JSON.stringify(item.value) + }) + }) let executeParams: DebugPluginRequest = { Code: "", @@ -239,6 +258,7 @@ export const LocalPluginExecuteDetailHeard: React.FC @@ -467,13 +488,13 @@ export const LocalPluginExecuteDetailHeard: React.FC = React.memo((props) => { - const {paramsList, pluginType, isExecuting} = props + const {paramsList, pluginType, isExecuting,jsonSchemaListRef} = props return ( <> {paramsList.map((item) => ( - + ))} @@ -481,7 +502,7 @@ export const ExecuteEnterNodeByPluginParams: React.FC = React.memo((props) => { - const {item, disabled, pluginType} = props + const {item, disabled, pluginType,jsonSchemaListRef} = props let extraSetting: FormExtraSettingProps | undefined = undefined try { extraSetting = JSON.parse(item.ExtraSetting || "{}") || { @@ -581,6 +602,7 @@ export const FormContentItemByType: React.FC = React extraSetting={extraSetting} codeType={pluginType} disabled={disabled} + jsonSchemaListRef={jsonSchemaListRef} /> ) } @@ -588,8 +610,9 @@ export const FormContentItemByType: React.FC = React /**执行表单单个项 */ export const OutputFormComponentsByType: React.FC = (props) => { - const {item, extraSetting, codeType, disabled, pluginType} = props + const {item, extraSetting, codeType, disabled, pluginType,jsonSchemaListRef} = props const [validateStatus, setValidateStatus] = useState<"success" | "error">("success") + const formProps = { rules: [{required: item.Required}], label: item.FieldVerbose || item.Field, @@ -645,11 +668,11 @@ export const OutputFormComponentsByType: React.FC - {/* */} + > + + ) case "text": @@ -762,6 +785,17 @@ export const OutputFormComponentsByType: React.FC ) + case "json": + if(typeof jsonSchemaListRef?.current !== "object") return <> + let schema: any = {} + try { + schema = JSON.parse(item.JsonSchema || "{}") + } catch (error) { + console.error("Parse JsonSchema failed:", error) + } + return default: return <> } diff --git a/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeardType.d.ts b/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeardType.d.ts index bd19cdcf0c..ec83e6561b 100644 --- a/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeardType.d.ts +++ b/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeardType.d.ts @@ -6,7 +6,7 @@ import {HTTPRequestBuilderParams} from "@/models/HTTPRequestBuilder" import {StreamResult} from "@/hook/useHoldGRPCStream/useHoldGRPCStreamType" import {FormInstance} from "antd" import {ExpandAndRetractExcessiveState} from "../expandAndRetract/ExpandAndRetract" - +import { JsonFormSchemaListWrapper } from "@/components/JsonFormWrapper/JsonFormWrapper" export interface PluginExecuteDetailHeardProps { token: string /**插件 */ @@ -41,13 +41,13 @@ export interface YakExtraParamProps { data: YakParamProps[] } -export interface ExecuteEnterNodeByPluginParamsProps { +export interface ExecuteEnterNodeByPluginParamsProps extends JsonFormSchemaListWrapper{ paramsList: YakParamProps[] pluginType?: string isExecuting: boolean } -export interface OutputFormComponentsByTypeProps { +export interface OutputFormComponentsByTypeProps extends JsonFormSchemaListWrapper{ item: YakParamProps extraSetting?: FormExtraSettingProps /**根据插件类型出编辑器类型/或者自己输入对应的编辑器类型 */ @@ -71,14 +71,14 @@ export interface PluginExecuteExtraFormValue extends HTTPRequestBuilderParams { /**前端使用,请求类型的选择 */ requestType: RequestType /**前端使用,请求类型》原始请求:数据包 */ - rawHTTPRequest:string + rawHTTPRequest: string } /**表单的key value类型 */ export interface CustomPluginExecuteFormValue { [key: string]: number | string | boolean | string[] | Uint8Array | KVPair[] | number[] } -export interface FormContentItemByTypeProps { +export interface FormContentItemByTypeProps extends JsonFormSchemaListWrapper{ item: YakParamProps pluginType?: string disabled?: boolean @@ -90,8 +90,8 @@ export interface PluginFixFormParamsProps { disabled: boolean /**原始请求中的数据包数据 */ rawHTTPRequest?: string - inputType?: "content"|"path" - setInputType?: (v:"content"|"path") => void + inputType?: "content" | "path" + setInputType?: (v: "content" | "path") => void } -export type RequestType = "original" | "input" | "httpFlowId" +export type RequestType = "original" | "input" | "httpFlowId" \ No newline at end of file diff --git a/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/PluginExecuteExtraParams.tsx b/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/PluginExecuteExtraParams.tsx index 86bea043b4..d9c0cd8220 100644 --- a/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/PluginExecuteExtraParams.tsx +++ b/app/renderer/src/main/src/pages/plugins/operator/localPluginExecuteDetailHeard/PluginExecuteExtraParams.tsx @@ -22,11 +22,12 @@ import {YakParamProps} from "../../pluginsType" import {defPluginExecuteFormValue} from "./constants" import {splitPluginParamsData} from "@/pages/pluginEditor/utils/convert" import {RemotePluginGV} from "@/enums/plugin" +import { JsonFormSchemaListWrapper } from "@/components/JsonFormWrapper/JsonFormWrapper" const {YakitPanel} = YakitCollapse type ExtraParamsValue = PluginExecuteExtraFormValue | CustomPluginExecuteFormValue -interface PluginExecuteExtraParamsProps { +interface PluginExecuteExtraParamsProps extends JsonFormSchemaListWrapper{ ref?: any pluginType: string /** 选填参数数据 */ @@ -53,7 +54,8 @@ const PluginExecuteExtraParams: React.FC = React. extraParamsValue, visible, setVisible, - onSave + onSave, + jsonSchemaListRef } = props const [form] = Form.useForm() @@ -112,7 +114,7 @@ const PluginExecuteExtraParams: React.FC = React. case "lua": return (
- +
已经到底啦~
) @@ -175,12 +177,12 @@ const PluginExecuteExtraParams: React.FC = React. ) export default PluginExecuteExtraParams -interface ExtraParamsNodeByTypeProps { +interface ExtraParamsNodeByTypeProps extends JsonFormSchemaListWrapper{ extraParamsGroup: YakExtraParamProps[] pluginType: string } export const ExtraParamsNodeByType: React.FC = React.memo((props) => { - const {extraParamsGroup, pluginType} = props + const {extraParamsGroup, pluginType,jsonSchemaListRef} = props const defaultActiveKey = useMemo(() => { return extraParamsGroup.map((ele) => ele.group) }, [extraParamsGroup]) @@ -190,7 +192,7 @@ export const ExtraParamsNodeByType: React.FC = React {item.data?.map((formItem) => ( - + ))} diff --git a/app/renderer/src/main/src/pages/plugins/operator/pluginExecuteResult/PluginExecuteResult.tsx b/app/renderer/src/main/src/pages/plugins/operator/pluginExecuteResult/PluginExecuteResult.tsx index e02d7f32cd..7e0c243e2c 100644 --- a/app/renderer/src/main/src/pages/plugins/operator/pluginExecuteResult/PluginExecuteResult.tsx +++ b/app/renderer/src/main/src/pages/plugins/operator/pluginExecuteResult/PluginExecuteResult.tsx @@ -315,8 +315,8 @@ const PluginExecuteHttpFlow: React.FC = React.mem ) }) /** 基础插件信息 / 日志 */ -const PluginExecuteLog: React.FC = React.memo((props) => { - const {loading, messageList} = props +export const PluginExecuteLog: React.FC = React.memo((props) => { + const {loading, messageList,wrapperClassName} = props const [activeKey, setActiveKey] = useState("plugin-log") const list: StreamResult.Log[] = useCreation(() => { @@ -385,7 +385,7 @@ const PluginExecuteLog: React.FC = React.memo((props) => activeKey={activeKey} onChange={onTabChange} type='line' - wrapperClassName={styles["plugin-execute-log"]} + wrapperClassName={classNames(styles["plugin-execute-log"],wrapperClassName)} > {logTabs.map((ele) => ( .ant-collapse-header { padding: 3px 0px 3px 0px !important; diff --git a/app/renderer/src/main/src/pages/risks/YakitRiskTable/YakitRiskTable.tsx b/app/renderer/src/main/src/pages/risks/YakitRiskTable/YakitRiskTable.tsx index c4af5c497a..7cfd2e8a9d 100644 --- a/app/renderer/src/main/src/pages/risks/YakitRiskTable/YakitRiskTable.tsx +++ b/app/renderer/src/main/src/pages/risks/YakitRiskTable/YakitRiskTable.tsx @@ -94,6 +94,7 @@ import {loadAuditFromYakURLRaw} from "@/pages/yakRunnerAuditCode/utils" import {AuditEmiterYakUrlProps} from "@/pages/yakRunnerAuditCode/YakRunnerAuditCodeType" import {CollapseList} from "@/pages/yakRunner/CollapseList/CollapseList" import {addToTab} from "@/pages/MainTabs" +import {YakCodemirror} from "@/components/yakCodemirror/YakCodemirror" export const isShowCodeScanDetail = (selectItem: Risk) => { const {ResultID, SyntaxFlowVariable, ProgramName} = selectItem @@ -1229,7 +1230,7 @@ export const YakitRiskTable: React.FC = React.memo((props)
Total - {response.Total} + {allTotal}
@@ -1734,7 +1735,6 @@ export const YakitCodeScanRiskDetails: React.FC = const {Body, ...auditYakUrl} = params const body = Body ? StringToUint8Array(Body) : undefined const result = await loadAuditFromYakURLRaw(auditYakUrl, body) - if (result && result.Resources.length > 0) { let arr: YakURLDataItemProps[] = [] result.Resources.filter((item) => item.ResourceType === "value").forEach((item) => { @@ -2098,7 +2098,20 @@ export const AuditResultCollapse: React.FC = React.mem } const renderItem = (info: YakURLDataItemProps) => { - return
{info.source}
+ const filename = info.code_range.url.split("/").pop() + const {start_line,end_line,source_code_line,start_column,end_column} = info.code_range + return ( + + ) } return (
diff --git a/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/BottomEditorDetails.tsx b/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/BottomEditorDetails.tsx index 74baaef1db..0efed4f9a2 100644 --- a/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/BottomEditorDetails.tsx +++ b/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/BottomEditorDetails.tsx @@ -354,7 +354,7 @@ export const BottomEditorDetails: React.FC = (props) = setTerminalRunnerId(runnnerId) setTerminalIds([...terminalIds, runnnerId]) - isShowEditorDetails && success(`终端${folderPathRef.current}监听成功`) + // isShowEditorDetails && success(`终端${folderPathRef.current}监听成功`) }) .catch((e: any) => { failed(`ERROR: ${JSON.stringify(e)}`) @@ -418,7 +418,7 @@ export const BottomEditorDetails: React.FC = (props) = const onListeningTerminalEnd = useMemoizedFn((data: {id: string; path: string}) => { const {id, path} = data if (getMapAllTerminalKey().includes(id)) { - isShowEditorDetails && warn(`终端${path}被关闭`) + // isShowEditorDetails && warn(`终端${path}被关闭`) // 列表关闭 if (terminalIds.length > 1) { if (terminalRunnerId === id) { @@ -456,14 +456,22 @@ export const BottomEditorDetails: React.FC = (props) = }) // grpc通知关闭 - const errorKey = "client-listening-terminal-end" - ipcRenderer.on(errorKey, (e: any, data: {id: string; path: string}) => { + const closeKey = "client-listening-terminal-end" + ipcRenderer.on(closeKey, (e: any, data: {id: string; path: string}) => { onListeningTerminalEnd(data) }) + + // grpc错误 + const errorKey = "client-listening-terminal-error" + ipcRenderer.on(errorKey, (e: any, data: {id: string; path: string}) => { + warn(`终端${data.path}错误`) + }) + return () => { // 移除 ipcRenderer.removeAllListeners(key) ipcRenderer.removeAllListeners(successKey) + ipcRenderer.removeAllListeners(closeKey) ipcRenderer.removeAllListeners(errorKey) // 清空 xtermClear(terminalRef) diff --git a/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.tsx b/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.tsx index 9e27e239da..ff5b2c6ad6 100644 --- a/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.tsx +++ b/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.tsx @@ -1,6 +1,4 @@ import React, {useEffect, useMemo, useRef, useState} from "react" -import {} from "antd" -import {} from "@ant-design/icons" import {useGetState, useMemoizedFn} from "ahooks" import {NetWorkApi} from "@/services/fetch" import {API} from "@/services/swagger/resposeType" diff --git a/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/TerminalBox/TerminalBox.tsx b/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/TerminalBox/TerminalBox.tsx index 237e3188ae..97fc23ec4a 100644 --- a/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/TerminalBox/TerminalBox.tsx +++ b/app/renderer/src/main/src/pages/yakRunner/BottomEditorDetails/TerminalBox/TerminalBox.tsx @@ -1,9 +1,5 @@ import React, {useEffect, useRef} from "react" -import {} from "@ant-design/icons" import {useMemoizedFn, useVirtualList} from "ahooks" -import {failed, success, warn} from "@/utils/notification" -import {writeXTerm, xtermClear} from "@/utils/xtermUtils" -import {Uint8ArrayToString} from "@/utils/str" import {getMapAllTerminalKey, getTerminalMap, setTerminalMap, TerminalDetailsProps} from "./TerminalMap" import YakitXterm from "@/components/yakitUI/YakitXterm/YakitXterm" import {OutlineTerminalIcon, OutlineTrashIcon} from "@/assets/icon/outline" diff --git a/app/renderer/src/main/src/pages/yakRunner/RunnerFileTree/RunnerFileTree.tsx b/app/renderer/src/main/src/pages/yakRunner/RunnerFileTree/RunnerFileTree.tsx index 8f69f21a51..eaf814bcd0 100644 --- a/app/renderer/src/main/src/pages/yakRunner/RunnerFileTree/RunnerFileTree.tsx +++ b/app/renderer/src/main/src/pages/yakRunner/RunnerFileTree/RunnerFileTree.tsx @@ -1,6 +1,6 @@ import React, {memo, useEffect, useMemo, useRef, useState} from "react" import {useMemoizedFn, useSize, useUpdateEffect} from "ahooks" -import {OpenedFileProps, RunnerFileTreeProps} from "./RunnerFileTreeType" +import {OpenedFileProps, OpenFolderDraggerProps, RunnerFileTreeProps} from "./RunnerFileTreeType" import {YakitButton} from "@/components/yakitUI/YakitButton/YakitButton" import {OutlinCompileIcon, OutlinePluscircleIcon, OutlineRefreshIcon, OutlineXIcon} from "@/assets/icon/outline" import {CollapseList} from "../CollapseList/CollapseList" @@ -52,9 +52,71 @@ import { } from "../FileTreeMap/ChildMap" import {v4 as uuidv4} from "uuid" import cloneDeep from "lodash/cloneDeep" -import {failed, success} from "@/utils/notification" +import {failed, success, warn} from "@/utils/notification" import {FileMonitorItemProps, FileMonitorProps} from "@/utils/duplex/duplex" import {Tooltip} from "antd" +import {getYakitEngineMode} from "@/constants/software" +import {showYakitModal} from "@/components/yakitUI/YakitModal/YakitModalConfirm" +import {YakitDragger} from "@/components/yakitUI/YakitForm/YakitForm" + +export const OpenFolderDragger: React.FC = (props) => { + const {setAbsolutePath} = props + const [value, setValue] = useState("") + return ( +
+ { + setValue(value) + setAbsolutePath(value) + }} + /> +
+ ) +} + +// 打开文件夹 +export const openFolder = () => { + if (getYakitEngineMode() === "remote") { + let absolutePath = "" + const m = showYakitModal({ + title: "请输入文件夹路径", + width: 400, + type: "white", + closable: false, + centered: true, + content: (absolutePath = v)} />, + onCancel: () => { + m.destroy() + }, + onOk: async () => { + if (absolutePath.length === 0) { + warn("请输入文件夹路径") + return + } + emiter.emit("onOpenFileTree", absolutePath) + m.destroy() + } + }) + } else { + ipcRenderer + .invoke("openDialog", { + title: "请选择文件夹", + properties: ["openDirectory"] + }) + .then((data: any) => { + if (data.filePaths.length) { + let absolutePath: string = data.filePaths[0].replace(/\\/g, "\\") + emiter.emit("onOpenFileTree", absolutePath) + } + }) + } +} const {ipcRenderer} = window.require("electron") @@ -183,7 +245,7 @@ export const RunnerFileTree: React.FC = (props) => { { key: "openFolder", label: "打开文件夹" - }, + } ] if (historyList.length > 0) { newMenu.push({ @@ -544,21 +606,6 @@ export const RunnerFileTree: React.FC = (props) => { } catch (error) {} }) - // 打开文件夹 - const openFolder = useMemoizedFn(() => { - ipcRenderer - .invoke("openDialog", { - title: "请选择文件夹", - properties: ["openDirectory"] - }) - .then((data: any) => { - if (data.filePaths.length) { - let absolutePath: string = data.filePaths[0].replace(/\\/g, "\\") - emiter.emit("onOpenFileTree", absolutePath) - } - }) - }) - // 打开历史 const openHistory = useMemoizedFn((key: string) => { const filterArr = historyList.filter((item) => item.path === key) diff --git a/app/renderer/src/main/src/pages/yakRunner/RunnerFileTree/RunnerFileTreeType.d.ts b/app/renderer/src/main/src/pages/yakRunner/RunnerFileTree/RunnerFileTreeType.d.ts index 8ba90157f8..c9f5e09081 100644 --- a/app/renderer/src/main/src/pages/yakRunner/RunnerFileTree/RunnerFileTreeType.d.ts +++ b/app/renderer/src/main/src/pages/yakRunner/RunnerFileTree/RunnerFileTreeType.d.ts @@ -2,6 +2,8 @@ export interface RunnerFileTreeProps { addFileTab: () => void } -export interface OpenedFileProps{ - -} \ No newline at end of file +export interface OpenedFileProps {} + +export interface OpenFolderDraggerProps { + setAbsolutePath: (v: string) => void +} diff --git a/app/renderer/src/main/src/pages/yakRunner/RunnerTabs/RunnerTabs.tsx b/app/renderer/src/main/src/pages/yakRunner/RunnerTabs/RunnerTabs.tsx index 7b545be384..7b877da493 100644 --- a/app/renderer/src/main/src/pages/yakRunner/RunnerTabs/RunnerTabs.tsx +++ b/app/renderer/src/main/src/pages/yakRunner/RunnerTabs/RunnerTabs.tsx @@ -77,6 +77,7 @@ import { YaklangLanguageSuggestionRequest } from "@/utils/monacoSpec/yakCompletionSchema" import {getModelContext} from "@/utils/monacoSpec/yakEditor" +import { openFolder } from "../RunnerFileTree/RunnerFileTree" const {ipcRenderer} = window.require("electron") @@ -1232,21 +1233,6 @@ export const YakRunnerWelcomePage: React.FC = memo((p } catch (error) {} }) - // 打开文件夹 - const openFolder = useMemoizedFn(() => { - ipcRenderer - .invoke("openDialog", { - title: "请选择文件夹", - properties: ["openDirectory"] - }) - .then((data: any) => { - if (data.filePaths.length) { - let absolutePath: string = data.filePaths[0].replace(/\\/g, "\\") - emiter.emit("onOpenFileTree", absolutePath) - } - }) - }) - return (
diff --git a/app/renderer/src/main/src/pages/yakRunner/YakRunner.tsx b/app/renderer/src/main/src/pages/yakRunner/YakRunner.tsx index 8cc2e45201..ccc3624298 100644 --- a/app/renderer/src/main/src/pages/yakRunner/YakRunner.tsx +++ b/app/renderer/src/main/src/pages/yakRunner/YakRunner.tsx @@ -462,11 +462,11 @@ export const YakRunner: React.FC = (props) => { const unTitleCountRef = useRef(1) const addFileTab = useThrottleFn( - () => { + (e?: any, params?: {name: string; code: string}) => { // 新建临时文件 const scratchFile: FileDetailInfo = { name: `Untitle-${unTitleCountRef.current}.yak`, - code: "# input your yak code\nprintln(`Hello Yak World!`)", + code: params?.code || "# input your yak code\nprintln(`Hello Yak World!`)", icon: "_f_yak", isActive: true, openTimestamp: moment().unix(), @@ -873,6 +873,18 @@ export const YakRunner: React.FC = (props) => { } }, []) + useEffect(() => { + // 调用打开临时文件 + ipcRenderer.on("fetch-send-to-yak-running", (e, res: any) => { + const {name = "", code = ""} = res || {} + if (!name || !code) return + addFileTab(e, {name, code}) + }) + return () => { + ipcRenderer.removeAllListeners("fetch-send-to-yak-running") + } + }, []) + return (
diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCode.module.scss b/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCode.module.scss index 0d53bcbb15..4b1490df1a 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCode.module.scss +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCode.module.scss @@ -10,12 +10,17 @@ height: 32px; display: flex; align-items: center; + justify-content: space-between; + gap: 8px; .title { color: #85899e; font-size: 12px; font-weight: 500; line-height: 16px; } + .extra { + flex: 1; + } } .textarea-box { padding: 0px 8px 8px 8px; @@ -36,6 +41,18 @@ flex: 1; overflow: hidden; } + .audit-log { + flex: 1; + overflow: auto; + .audit-log-wrapper { + overflow: auto; + :global { + .ant-tabs-nav { + display: none; + } + } + } + } } .audit-form-footer { @@ -293,81 +310,18 @@ padding: 12px 16px; flex: 1; overflow: hidden; - .table-header { + .audit-virtual-list { + height: 100%; + } + .audit-text { + overflow: hidden; + } + + .audit-opt { display: flex; flex-direction: row; - gap: 16px; - padding: 8px 12px; - font-size: 12px; - font-weight: 600; - line-height: 16px; - position: fixed; - width: calc(100% - 32px); - background: #ffffff; - z-index: 1; - border-bottom: 1px solid #eaecf3; - .audit-name { - flex: 1; - display: flex; - flex-direction: row; - gap: 4px; - align-items: center; - } - .audit-path { - flex: 2; - } - .audit-time { - width: 200px; - } - .audit-opt { - width: 120px; - } - } - .table-content { - padding-top: 32px; - overflow: auto; - height: 100%; - .table-item { - display: flex; - flex-direction: row; - border-bottom: 1px solid #eaecf3; - gap: 16px; - padding: 8px 12px; - .audit-name { - flex: 1; - display: flex; - flex-direction: row; - gap: 4px; - align-items: center; - } - .audit-path { - display: flex; - align-items: center; - flex: 2; - } - .audit-time { - display: flex; - align-items: center; - width: 200px; - } - .audit-opt { - width: 120px; - display: flex; - flex-direction: row; - align-items: center; - gap: 12px; - .to-icon { - svg { - color: #85899e; - } - &:hover { - svg { - color: #f28b44; - } - } - } - } - } + align-items: center; + gap: 12px; } } } @@ -391,3 +345,89 @@ border-top: 1px dashed #eaecf3; } } + +.project-manager-edit-form { + padding: 8px 24px 8px 24px; + .opt-btn { + display: flex; + flex-direction: row; + justify-content: flex-end; + gap: 12px; + margin-bottom: 16px; + } +} + +.extra-icon { + width: 16px; + height: 16px; + box-sizing: content-box; + padding: 4px; + border-radius: 4px; + cursor: pointer; + color: #85899e; + svg { + width: 16px; + height: 16px; + } + &:hover { + background-color: var(--yakit-background-color); + color: var(--yakit-primary-5); + } +} + +.audit-modal-form { + :global { + .json-schema-form { + .ant-form-item { + display: flex; + flex-direction: row; + .ant-form-item-label { + flex: 8; + text-align: right; + label::after { + content: ":"; + position: relative; + top: -0.5px; + margin: 0 8px 0 2px; + z-index: 1; + display: inline-flex; + align-items: center; + } + } + .ant-form-item-control { + flex: 16; + } + + .ant-form-item-label { + // 仅在相邻元素不是 .ant-form-item-control 时应用样式 + &:not(:has(+ .ant-form-item-control)) { + display: none; + width: 100%; + padding: 0; + margin-bottom: 20px; + text-align: left; + border: 0; + border-bottom: 1px solid #e5e5e5; + label { + font-size: 21px !important; + color: #333; + } + label::after { + content: ""; + position: relative; + top: -0.5px; + margin: 0 8px 0 2px; + z-index: 1; + display: inline-flex; + align-items: center; + } + } + } + } + // 需询问后端是否可从schema中移除 + #root_auth__error,#root_proxy__error { + display: none; + } + } + } +} diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCode.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCode.tsx index 91a0f8ec92..25ff94df79 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCode.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCode.tsx @@ -1,16 +1,24 @@ import React, {memo, useEffect, useMemo, useRef, useState} from "react" import { + AfreshAuditModalProps, AuditCodeProps, + AuditHistoryTableProps, + AuditMainItemFormProps, + AuditModalFormModalProps, + AuditModalFormProps, AuditNodeDetailProps, AuditNodeProps, AuditTreeNodeProps, AuditTreeProps, - AuditYakUrlProps + AuditYakUrlProps, + ProjectManagerEditFormProps, + QuerySSAProgramsProps, + SSAProgramResponse } from "./AuditCodeType" import classNames from "classnames" import styles from "./AuditCode.module.scss" -import {YakScript} from "@/pages/invoker/schema" -import {Divider, Form, FormInstance, Tooltip, Tree} from "antd" +import {genDefaultPagination, QueryGeneralResponse, YakScript} from "@/pages/invoker/schema" +import {Badge, Divider, Form, FormInstance, Input, Progress, Tooltip, Tree} from "antd" import {YakitSpin} from "@/components/yakitUI/YakitSpin/YakitSpin" import {ExtraParamsNodeByType} from "@/pages/plugins/operator/localPluginExecuteDetailHeard/PluginExecuteExtraParams" import {ExecuteEnterNodeByPluginParams} from "@/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard" @@ -21,13 +29,22 @@ import { onCodeToInfo, ParamsToGroupByGroupName } from "@/pages/plugins/editDetails/utils" -import {useDebounceFn, useInViewport, useMemoizedFn, useSize, useThrottleEffect, useUpdateEffect} from "ahooks" +import { + useControllableValue, + useDebounceFn, + useInterval, + useInViewport, + useMemoizedFn, + useSize, + useThrottleEffect, + useUpdateEffect +} from "ahooks" import {grpcFetchLocalPluginDetail} from "@/pages/pluginHub/utils/grpc" import {YakitButton} from "@/components/yakitUI/YakitButton/YakitButton" import {apiDebugPlugin, DebugPluginRequest} from "@/pages/plugins/utils" import {HTTPRequestBuilderParams} from "@/models/HTTPRequestBuilder" import useHoldGRPCStream from "@/hook/useHoldGRPCStream/useHoldGRPCStream" -import {failed, yakitNotify} from "@/utils/notification" +import {failed, warn, yakitNotify} from "@/utils/notification" import {randomString} from "@/utils/randomUtil" import {CustomPluginExecuteFormValue} from "@/pages/plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeardType" import {defPluginExecuteFormValue} from "@/pages/plugins/operator/localPluginExecuteDetailHeard/constants" @@ -35,18 +52,19 @@ import useStore from "../hooks/useStore" import {getNameByPath, grpcFetchAuditTree, grpcFetchDeleteAudit, loadAuditFromYakURLRaw} from "../utils" import {YakitEmpty} from "@/components/yakitUI/YakitEmpty/YakitEmpty" import { - OutlinCompileIcon, OutlineArrowcirclerightIcon, OutlineBugIcon, OutlineChevronrightIcon, OutlineDeprecatedIcon, + OutlineDocumentduplicateIcon, + OutlinePencilaltIcon, + OutlineReloadScanIcon, OutlineScanIcon, OutlineSearchIcon, OutlineTrashIcon, OutlineXIcon } from "@/assets/icon/outline" import emiter from "@/utils/eventBus/eventBus" -import {YakitTextArea} from "@/components/yakitUI/YakitTextArea/YakitTextArea" import {LoadingOutlined} from "@ant-design/icons" import {StringToUint8Array} from "@/utils/str" import {clearMapAuditDetail, getMapAuditDetail, setMapAuditDetail} from "./AuditTree/AuditMap" @@ -64,7 +82,7 @@ import {YakitPopconfirm} from "@/components/yakitUI/YakitPopconfirm/YakitPopconf import {YakitInput} from "@/components/yakitUI/YakitInput/YakitInput" import {CodeRangeProps} from "../RightAuditDetail/RightAuditDetail" import {formatTimestamp} from "@/utils/timeUtil" -import {QuestionMarkCircleIcon} from "@/assets/newIcon" +import {QuestionMarkCircleIcon, TrashIcon} from "@/assets/newIcon" import {addToTab} from "@/pages/MainTabs" import {YakitRoute} from "@/enums/yakitRoute" import {AuditCodePageInfoProps} from "@/store/pageInfo" @@ -73,7 +91,18 @@ import {QuerySyntaxFlowResultResponse} from "@/pages/yakRunnerCodeScan/YakRunner import {YakitModal} from "@/components/yakitUI/YakitModal/YakitModal" import {AuditCodeStatusInfo} from "../YakRunnerAuditCode" import {StreamResult} from "@/hook/useHoldGRPCStream/useHoldGRPCStreamType" -import { JumpToEditorProps } from "../BottomEditorDetails/BottomEditorDetailsType" +import {JumpToEditorProps} from "../BottomEditorDetails/BottomEditorDetailsType" +import {YakitVirtualList} from "@/components/yakitUI/YakitVirtualList/YakitVirtualList" +import {VirtualListColumns} from "@/components/yakitUI/YakitVirtualList/YakitVirtualListType" +import {YakitFormDragger} from "@/components/yakitUI/YakitForm/YakitForm" +import {YakitSelect} from "@/components/yakitUI/YakitSelect/YakitSelect" +import {PluginExecuteLog} from "@/pages/plugins/operator/pluginExecuteResult/PluginExecuteResult" +import useDispatcher from "../hooks/useDispatcher" +import {Paging} from "@/utils/yakQueryHTTPFlow" +import {YakitTag} from "@/components/yakitUI/YakitTag/YakitTag" +import {showYakitModal} from "@/components/yakitUI/YakitModal/YakitModalConfirm" +import {setClipboardText} from "@/utils/clipboard" +import {getJsonSchemaListResult} from "@/components/JsonFormWrapper/JsonFormWrapper" const {ipcRenderer} = window.require("electron") @@ -358,21 +387,27 @@ export const AuditTree: React.FC = memo((props) => { const TopId = "top-message" export const AuditCode: React.FC = (props) => { - const {projectName, pageInfo} = useStore() + const {setOnlyFileTree} = props + const {projectName, pageInfo, auditRule, auditExecuting} = useStore() + const {setAuditExecuting} = useDispatcher() - const [value, setValue] = useState("") const [loading, setLoading] = useState(false) const [isShowEmpty, setShowEmpty] = useState(false) const [expandedKeys, setExpandedKeys] = React.useState([]) const [foucsedKey, setFoucsedKey] = React.useState("") - const [refreshTree, setRefreshTree] = useState(false) - const [disabled, setDisabled] = useState(true) + // 已审计的参数Query用于加载更多时使用 + const runQueryRef = useRef< + { + Key: string + Value: number + }[] + >() const initAuditTree = useMemoizedFn((ids: string[], depth: number) => { return ids.map((id) => { const itemDetail = getMapAuditDetail(id) - let obj: AuditNodeProps = {...itemDetail, depth} + let obj: AuditNodeProps = {...itemDetail, depth, query: runQueryRef.current} const childArr = getMapAuditChildDetail(id) if (itemDetail.ResourceType === "variable" || itemDetail.ResourceType === TopId) { @@ -439,10 +474,9 @@ export const AuditCode: React.FC = (props) => { Path: path } } - // 如若已输入代码审计框 - if (!disabled && (params?.Query || []).length > 0) { - params.Query = [] - } + // 沿用审计时的Query值 + params.Query = runQueryRef.current + const result = await loadAuditFromYakURLRaw(params, body) if (result) { @@ -492,144 +526,219 @@ export const AuditCode: React.FC = (props) => { }) useUpdateEffect(() => { - setValue("") - setDisabled(true) resetMap() setRefreshTree(!refreshTree) }, [projectName]) - const onSubmit = useMemoizedFn(async () => { - try { - resetMap() - setLoading(true) - setShowEmpty(false) - const path: string = "/" - let params: AuditYakUrlProps = { - Schema: "syntaxflow", - Location: projectName || "", - Path: path - } - const body: Buffer = StringToUint8Array(value) - lastValue.current = value - if (pageInfo) { - const {Variable, Value, ...rest} = pageInfo - // 此处请求Path固定为/ 因为不用拼接Variable、Value - params = rest - // 默认展开项 - if (Variable) { - setExpandedKeys([`${pageInfo.Path}${Variable}`]) + const onAuditRuleSubmitFun = useMemoizedFn( + async (textArea: string = "", Query?: {Key: string; Value: number}[]) => { + try { + resetMap() + setLoading(true) + setShowEmpty(false) + setOnlyFileTree(false) + const path: string = "/" + let params: AuditYakUrlProps = { + Schema: "syntaxflow", + Location: projectName || "", + Path: path } - } - // 如若已输入代码审计框 - if (!disabled && (params?.Query || []).length > 0) { - params.Query = [] - } - const result = await loadAuditFromYakURLRaw(params, body) - if (result && result.Resources.length > 0) { - let messageIds: string[] = [] - let variableIds: string[] = [] - // 构造树结构 - result.Resources.filter((item) => item.VerboseType !== "result_id").forEach((item, index) => { - const {ResourceType, VerboseType, VerboseName, ResourceName, Size, Extra} = item - // 警告信息(置顶显示)前端收集折叠 - if (ResourceType === "message") { - const id = `${TopId}${path}${VerboseName}-${index}` - messageIds.push(id) - setMapAuditDetail(id, { - parent: path, - id, - name: VerboseName, - ResourceType, - VerboseType, - Size, - Extra - }) + const body: Buffer = StringToUint8Array(textArea) + lastValue.current = textArea + if (pageInfo) { + const {Variable, Value, ...rest} = pageInfo + // 此处请求Path固定为/ 因为不用拼接Variable、Value + params = rest + // 默认展开项 + if (Variable) { + setExpandedKeys([`${pageInfo.Path}${Variable}`]) } - // 变量 - if (ResourceType === "variable") { - const id = `${path}${ResourceName}` - variableIds.push(id) - setMapAuditDetail(id, { + } + // 如若已输入代码审计框 + if (auditRule && (params?.Query || []).length > 0) { + params.Query = [] + } + if (Query) { + params.Query = Query + } + runQueryRef.current = params.Query + const result = await loadAuditFromYakURLRaw(params, body) + if (result && result.Resources.length > 0) { + let messageIds: string[] = [] + let variableIds: string[] = [] + // 构造树结构 + result.Resources.filter((item) => item.VerboseType !== "result_id").forEach((item, index) => { + const {ResourceType, VerboseType, VerboseName, ResourceName, Size, Extra} = item + // 警告信息(置顶显示)前端收集折叠 + if (ResourceType === "message") { + const id = `${TopId}${path}${VerboseName}-${index}` + messageIds.push(id) + setMapAuditDetail(id, { + parent: path, + id, + name: VerboseName, + ResourceType, + VerboseType, + Size, + Extra + }) + } + // 变量 + if (ResourceType === "variable") { + const id = `${path}${ResourceName}` + variableIds.push(id) + setMapAuditDetail(id, { + parent: path, + id, + name: ResourceName, + ResourceType, + VerboseType, + Size, + Extra + }) + } + }) + let topIds: string[] = [] + if (messageIds.length > 0) { + topIds.push(TopId) + setMapAuditDetail(TopId, { parent: path, - id, - name: ResourceName, - ResourceType, - VerboseType, - Size, - Extra + id: TopId, + name: "message", + ResourceType: TopId, + VerboseType: "", + Size: 0, + Extra: [] }) + setMapAuditChildDetail(TopId, messageIds) } - }) - let topIds: string[] = [] - if (messageIds.length > 0) { - topIds.push(TopId) - setMapAuditDetail(TopId, { - parent: path, - id: TopId, - name: "message", - ResourceType: TopId, - VerboseType: "", - Size: 0, - Extra: [] - }) - setMapAuditChildDetail(TopId, messageIds) + setMapAuditChildDetail("/", [...topIds, ...variableIds]) + setRefreshTree(!refreshTree) + } else { + setShowEmpty(true) } - setMapAuditChildDetail("/", [...topIds, ...variableIds]) - setRefreshTree(!refreshTree) - } else { + setLoading(false) + } catch (error: any) { + failed(`${error}`) setShowEmpty(true) + setLoading(false) } - setLoading(false) - } catch (error: any) { - failed(`${error}`) - setShowEmpty(true) - setLoading(false) + } + ) + + const tokenRef = useRef(randomString(40)) + /** 是否在执行中 */ + const [runtimeId, setRuntimeId] = useState("") + const logInfoRef = useRef([]) + const progressRef = useRef(0) + const [resultInfo, setResultInfo] = useState<{progress: number; logState: StreamResult.Log[]}>() + const [interval, setInterval] = useState() + const [streamInfo, debugPluginStreamEvent] = useHoldGRPCStream({ + taskName: "debug-plugin", + apiKey: "DebugPlugin", + token: tokenRef.current, + onEnd: () => { + debugPluginStreamEvent.stop() + setAuditExecuting && setAuditExecuting(false) + setTimeout(() => { + setInterval(undefined) + }, 500) + }, + onError: () => {}, + setRuntimeId: (rId) => { + yakitNotify("info", `调试任务启动成功,运行时 ID: ${rId}`) + setRuntimeId(rId) } }) - // 获取文本域输入框 - const onGrpcSetTextArea = useMemoizedFn((arr: {Key: string; Value: number}[]) => { - let resultId: number | null = null - arr.forEach((item) => { - if (item.Key === "result_id") { - resultId = item.Value - } - }) - if (resultId) { - const Pagination = { - Page: 1, - Limit: 10, - Order: "desc", - OrderBy: "Id" - } - apiFetchQuerySyntaxFlowResult({ - Pagination, - Filter: { - TaskIDs: [], - ResultIDs: [resultId], - RuleNames: [], - ProgramNames: [], - Keyword: "", - OnlyRisk: false - } + useEffect(() => { + const progress = Math.floor((streamInfo.progressState.map((item) => item.progress)[0] || 0) * 100) / 100 + progressRef.current = progress + logInfoRef.current = streamInfo.logState.slice(0, 8) + // 当任务结束时 + if (streamInfo.logState[0]?.level === "json") { + onCancelAuditStream() + onAuditRuleSubmitFun("", [{Key: "result_id", Value: streamInfo.logState[0].data}]) + return + } + }, [streamInfo]) + + useInterval( + () => { + setResultInfo({ + progress: progressRef.current, + logState: logInfoRef.current }) - .then((rsp: QuerySyntaxFlowResultResponse) => { - const resData = rsp?.Results || [] - if (resData.length > 0) { - setValue(resData[0].RuleContent) - } - }) - .catch(() => {}) + }, + interval, + { + immediate: true } + ) + + // 流式审计 PS:流式审计成功后,根据result_id走正常结构查询 + const onAuditStreamRuleSubmitFun = useMemoizedFn(async (textArea: string = "") => { + if (!textArea) { + warn("请输入规则") + return + } + logInfoRef.current = [] + progressRef.current = 0 + debugPluginStreamEvent.reset() + setRuntimeId("") + const requestParams: DebugPluginRequest = { + Code: "", + PluginType: "yak", + Input: "", + HTTPRequestTemplate: {} as HTTPRequestBuilderParams, + ExecParams: [ + { + Key: "programName", + Value: projectName || "" + }, + { + Key: "ruleContext", + Value: textArea + } + ], + PluginName: "SyntaxFlow 规则执行" + } + apiDebugPlugin({params: requestParams, token: tokenRef.current}) + .then(() => { + setAuditExecuting && setAuditExecuting(true) + setOnlyFileTree(false) + debugPluginStreamEvent.start() + setInterval(500) + }) + .catch(() => { + onAuditRuleSubmitFun(textArea) + }) + }) + + const onCancelAuditStream = () => { + debugPluginStreamEvent.cancel() + debugPluginStreamEvent.reset() + } + + // 停止 + const onStopAuditRuleFun = useMemoizedFn(() => { + onCancelAuditStream() }) useEffect(() => { - setDisabled(true) - if (pageInfo) { - onGrpcSetTextArea(pageInfo.Query) - onSubmit() + emiter.on("onAuditRuleSubmit", onAuditStreamRuleSubmitFun) + emiter.on("onStopAuditRule", onStopAuditRuleFun) + return () => { + emiter.off("onAuditRuleSubmit", onAuditStreamRuleSubmitFun) + emiter.off("onStopAuditRule", onStopAuditRuleFun) + } + }, []) + + useEffect(() => { + if (pageInfo?.Query) { + // 页面跳转时,自动执行 无需流式审计 + onAuditRuleSubmitFun() } else { - setValue("") resetMap() setRefreshTree(!refreshTree) } @@ -644,7 +753,8 @@ export const AuditCode: React.FC = (props) => { if (arr.length > 0) { const hash = arr[0].Value setBugId(node.id) - emiter.emit("onCodeAuditOpenRightBugDetail", hash) + emiter.emit("onCodeAuditOpenBugDetail", hash) + emiter.emit("onCodeAuditOpenBottomDetail", JSON.stringify({type: "holeDetail"})) } } catch (error) { failed(`打开错误${error}`) @@ -656,7 +766,7 @@ export const AuditCode: React.FC = (props) => { Schema: "syntaxflow", Location: projectName || "", Path: node.id, - Body: value + Body: auditRule } if (pageInfo) { const {...rest} = pageInfo @@ -665,61 +775,120 @@ export const AuditCode: React.FC = (props) => { Path: node.id } } + if (node.query) { + rightParams.Query = node.query + } emiter.emit("onCodeAuditOpenRightDetail", JSON.stringify(rightParams)) } }) - const setTextArea = useMemoizedFn((value: string) => { - setDisabled(false) - setValue(value) - }) return (
代码审计
+ {auditExecuting && ( +
+ +
+ )}
- -
- -
- {isShowEmpty ? (
暂无数据
) : ( - + <> + {auditExecuting ? ( +
+ +
+ ) : ( + + )} + )}
) } -interface AuditModalFormProps { - onCancel: () => void - // 拆分后不在有默认值 - isInitDefault?: boolean - isExecuting: boolean - onStartAudit: (path: string, v: DebugPluginRequest) => void +// 审计表单主要项内容 +export const AuditMainItemForm: React.FC = (props) => { + const [type, setType] = useState("local") + return ( +
+ + { + setType(value) + }} + > + 本地文件 + 压缩包 + Git/SVN仓库 + Jar + + + + {type !== "jar" && ( + + + Yaklang + + + )} + {type === "git/svn" && ( + <> + + + + + + + + + + + )} + + + + + + +
+ ) } export const AuditModalForm: React.FC = (props) => { @@ -729,6 +898,9 @@ export const AuditModalForm: React.FC = (props) => { const [plugin, setPlugin] = useState() const [form] = Form.useForm() + const jsonSchemaListRef = useRef<{ + [key: string]: any + }>({}) const cacheRef = useRef() // 获取参数 @@ -843,6 +1015,18 @@ export const AuditModalForm: React.FC = (props) => { if (value?.ProgramPath) { cacheRef.current.onSetRemoteValues(value?.ProgramPath) } + + const result = getJsonSchemaListResult(jsonSchemaListRef.current) + if (result.jsonSchemaError.length > 0) { + failed(`jsonSchema校验失败`) + return + } + result.jsonSchemaSuccess.forEach((item) => { + requestParams.ExecParams.push({ + Key: item.key, + Value: JSON.stringify(item.value) + }) + }) onStartAudit(value["programName"], requestParams) }) .catch(() => {}) @@ -863,12 +1047,15 @@ export const AuditModalForm: React.FC = (props) => { /* eslint-disable no-template-curly-in-string */ required: "${label} 是必填字段" }} + className={styles["audit-modal-form"]} > + {/* */}
{groupParams.length > 0 ? ( @@ -877,7 +1064,11 @@ export const AuditModalForm: React.FC = (props) => {
额外参数 (非必填)
- + ) : null} @@ -891,16 +1082,9 @@ export const AuditModalForm: React.FC = (props) => { ) } -interface AuditModalFormModalProps { - onCancel: () => void - onSuccee: (path: string) => void - isInitDefault?: boolean - title?: string -} - -// 公共封装组件用于新建项目 +// 公共封装组件用于编译项目 export const AuditModalFormModal: React.FC = (props) => { - const {onCancel, onSuccee, isInitDefault, title} = props + const {onCancel, onSuccee, isInitDefault, title, warrpId} = props const [isShowCompileModal, setShowCompileModal] = useState(true) const tokenRef = useRef(randomString(40)) const [isShowRunAuditModal, setShowRunAuditModal] = useState(false) @@ -970,10 +1154,11 @@ export const AuditModalFormModal: React.FC = (props) = Progress: progress }) }, [streamInfo]) + return ( <> = (props) = {/* 编译项目进度条弹窗 */} = (props) = ) } -interface AuditHistoryTableProps { - setShowAuditList: (v: boolean) => void +// 公共封装组件用于重新编译 +export const AfreshAuditModal: React.FC = (props) => { + const {afreshName, setAfreshName, onSuccee, warrpId} = props + const tokenRef = useRef(randomString(40)) + /** 是否在执行中 */ + const [isExecuting, setIsExecuting] = useState(false) + const [runtimeId, setRuntimeId] = useState("") + const [streamInfo, debugPluginStreamEvent] = useHoldGRPCStream({ + taskName: "debug-plugin", + apiKey: "DebugPlugin", + token: tokenRef.current, + onEnd: () => { + debugPluginStreamEvent.stop() + setTimeout(() => { + setIsExecuting(false) + }, 300) + }, + onError: () => {}, + setRuntimeId: (rId) => { + yakitNotify("info", `调试任务启动成功,运行时 ID: ${rId}`) + setRuntimeId(rId) + } + }) + // export-show + const [exportStreamData, setExportStreamData] = useState({ + Progress: 0, + Message: "", + CostDurationVerbose: "", + RestDurationVerbose: "", + Speed: "0" + }) + const logInfoRef = useRef([]) + + useEffect(() => { + // 初次打开时带参执行 + if (afreshName) { + const requestParams: DebugPluginRequest = { + Code: "", + PluginType: "yak", + Input: "", + HTTPRequestTemplate: {} as HTTPRequestBuilderParams, + ExecParams: [ + { + Key: "programName", + Value: afreshName + } + ], + PluginName: "SSA 项目重编译" + } + + debugPluginStreamEvent.reset() + setRuntimeId("") + apiDebugPlugin({params: requestParams, token: tokenRef.current}).then(() => { + setIsExecuting(true) + debugPluginStreamEvent.start() + }) + } + }, [afreshName]) + + const onCancelAudit = () => { + logInfoRef.current = [] + debugPluginStreamEvent.cancel() + debugPluginStreamEvent.reset() + setAfreshName(undefined) + } + + useEffect(() => { + const progress = Math.floor((streamInfo.progressState.map((item) => item.progress)[0] || 0) * 100) / 100 + // 当任务结束时 跳转打开编译列表 + if (progress === 1) { + setTimeout(() => { + logInfoRef.current = [] + onCancelAudit() + onSuccee() + }, 300) + } + logInfoRef.current = streamInfo.logState.slice(0, 8) + setExportStreamData({ + ...exportStreamData, + Progress: progress + }) + }, [streamInfo]) + + return ( + <> + {/* 重新编译项目进度条弹窗 */} + + { + onCancelAudit() + }} + logInfo={logInfoRef.current} + showDownloadDetail={false} + /> + + + ) } +export const ProjectManagerEditForm: React.FC = memo((props) => { + const {onClose, record, setData} = props + const {Name, Description} = record + const [form] = Form.useForm() + + useEffect(() => { + if (record) { + form.setFieldsValue({ + Name, + Description + }) + } + }, []) + + const onFinish = useMemoizedFn(async (v: {Name: string; Description: string}) => { + ipcRenderer + .invoke("UpdateSSAProgram", { + ProgramInput: v + }) + .then(() => { + setData((pre) => + pre.map((item) => { + if (item.Name === v.Name) { + return { + ...item, + Description: v.Description + } + } + return item + }) + ) + onClose() + }) + .catch((e) => { + yakitNotify("error", "编辑列表数据失败:" + e) + }) + }) + + return ( +
+
onFinish(v)}> + + + + + + +
+ { + onClose() + }} + type='outline2' + > + 取消 + + + 保存 + +
+
+
+ ) +}) + export const AuditHistoryTable: React.FC = memo((props) => { - const {setShowAuditList} = props + const {pageType, onClose, onExecuteAudit, warrpId} = props const {projectName} = useStore() + const [afreshName, setAfreshName] = useState() + const [refresh, setRefresh] = useControllableValue(props, { + defaultValue: false, + valuePropName: "refresh", + trigger: "setRefresh" + }) + const [params, setParams] = useState({ + Keyword: "" + }) + const [pagination, setPagination] = useState(genDefaultPagination(20)) const [loading, setLoading] = useState(false) - const [aduitData, setAduitData] = useState() - const [search, setSearch] = useState() - useEffect(() => { - getAduitList() - }, [search]) + const [data, setData] = useState([]) + const [total, setTotal] = useState(0) + const [hasMore, setHasMore] = useState(false) + + const [isAllSelect, setIsAllSelect] = useState(false) + const [selectedRowKeys, setSelectedRowKeys] = useState([]) + // 接口是否正在请求 + const isGrpcRef = useRef(false) + const afterId = useRef() + + const onSelectAll = useMemoizedFn(() => { + if (isAllSelect) { + setIsAllSelect(false) + setSelectedRowKeys([]) + } else { + setIsAllSelect(true) + setSelectedRowKeys(data.map((item) => item.Id)) + } + }) + const onSelectChange = useMemoizedFn((c: boolean, keys: string, rows: SSAProgramResponse) => { + if (c) { + setSelectedRowKeys([...selectedRowKeys, rows.Id]) + } else { + setIsAllSelect(false) + const newSelectedRowKeys = selectedRowKeys.filter((ele) => ele !== rows.Id) + setSelectedRowKeys(newSelectedRowKeys) + } + }) - const getAduitList = useMemoizedFn(async () => { - try { + useEffect(() => { + update(true) + }, [refresh]) + + const update = useMemoizedFn(async (reload?: boolean, page?: number, limit?: number) => { + if (isGrpcRef.current) return + isGrpcRef.current = true + const paginationProps = { + ...pagination, + Page: page || 1, + Limit: limit || pagination.Limit, + } + if (reload) { + afterId.current = undefined setLoading(true) - const {res} = await grpcFetchAuditTree("/") - if (res.Resources.length === 0) { - setShowAuditList(false) - return - } - if (search && search.length > 0) { - const newResources = res.Resources.filter((item) => JSON.stringify(item).includes(search)) - const obj: RequestYakURLResponse = { - ...res, - Resources: newResources, - Total: newResources.length - } - setAduitData(obj) - } else { - setAduitData(res) - } - setLoading(false) - } catch (error) { - setLoading(false) + setRefresh(!refresh) } - }) - const getAuditPath = useMemoizedFn((val: YakURLResource) => { - let path: string = "-" - let time: string = "-" - let description: string = "" - let language: string = "" - val.Extra.forEach((item) => { - switch (item.Key) { - case "Path": - path = item.Value - break - case "Description": - description = item.Value - break - case "Language": - language = item.Value - break - case "CreateAt": - time = formatTimestamp(parseInt(item.Value)) - break - } - }) + ipcRenderer + .invoke("QuerySSAPrograms", { + Filter: {...params, AfterID: reload ? undefined : parseInt(afterId.current + "")}, + Pagination: paginationProps + }) + .then((item: QueryGeneralResponse) => { + const newData = reload ? item.Data : data.concat(item.Data) + const isMore = item.Data.length < item.Pagination.Limit || newData.length === total + setHasMore(!isMore) + if (isAllSelect) setSelectedRowKeys(newData.map((item) => item.Id)) + + setData(newData) + setPagination(item.Pagination || genDefaultPagination(200)) + if (reload) { + setTotal(item.Total) + } else { + getTotalFun() + } + }) + .catch((e) => { + yakitNotify("error", "获取列表数据失败:" + e) + }) + .finally(() => + setTimeout(() => { + setLoading(false) + isGrpcRef.current = false + }, 300) + ) + }) - return { - path, - time, - description, - language + const getTotalFun = useMemoizedFn(() => { + const paginationProps = { + ...pagination, + Page: 1, + Limit: pagination.Limit, } + ipcRenderer + .invoke("QuerySSAPrograms", { + Filter: params, + Pagination: paginationProps + }) + .then((item: QueryGeneralResponse) => { + setTotal(item.Total) + }) }) - const onDelete = useMemoizedFn(async (path: string) => { + const onDelete = useMemoizedFn(async (params: {DeleteAll?: boolean; Filter?: QuerySSAProgramsProps}) => { try { setLoading(true) - await grpcFetchDeleteAudit(path) - getAduitList() - - if (path === `/${projectName}`) { - emiter.emit("onResetAuditStatus") - } + ipcRenderer + .invoke("DeleteSSAPrograms", { + ...params + }) + .then(() => { + update(true) + setIsAllSelect(false) + setSelectedRowKeys([]) + }) } catch (error) { setLoading(false) failed(`删除失败${error}`) } }) - return ( - -
event.stopPropagation()}> -
-
-
已编译项目
-
-
Total
-
{aduitData?.Total}
+ const columns: VirtualListColumns[] = [ + { + title: "项目名称", + dataIndex: "Name", + render: (text, record) => { + return ( +
+ {record.Name} +
+ ) + } + }, + { + title: "语言", + dataIndex: "Language", + width: 60 + }, + { + title: "项目描述", + dataIndex: "Description", + render: (text, record) => { + return ( + +
{text}
+
+ ) + } + }, + { + title: "存储路径", + dataIndex: "Dbpath", + render: (text, record) => { + return ( + <> +
+ {record.Dbpath}
- {/* + +
setClipboardText(record.Dbpath)}> + +
+
+ + ) + } + }, + { + title: "漏洞数", + dataIndex: "LowRiskNumber", + render: (text, record) => { + const {CriticalRiskNumber, HighRiskNumber, WarnRiskNumber, LowRiskNumber} = record + const countNum = + parseInt(CriticalRiskNumber + "") + + parseInt(HighRiskNumber + "") + + parseInt(WarnRiskNumber + "") + + parseInt(LowRiskNumber + "") + return <>{countNum !== 0 ? {countNum} : "-"} + }, + width: 120 + }, + { + title: "编译时间", + dataIndex: "CreateAt", + render: (text, record) => { + return
{formatTimestamp(text)}
+ }, + width: 140 + }, + { + title: "操作", + dataIndex: "action", + width: 200, + render: (text, record) => { + return ( +
+ + + } + onClick={() => { + setAfreshName(record.Name) + }} + /> + + + + + } + onClick={() => { + emiter.emit( + "openPage", + JSON.stringify({ + route: YakitRoute.YakRunner_Code_Scan, + params: { + projectName: record.Name + } + }) + ) + if (pageType === "aucitCode") { + onClose && onClose() + } + }} + /> + + + {/* 此处的Tooltip会导致页面抖动(待处理) */} + + } + onClick={() => { + if (pageType === "projectManager") { + // 跳转到审计页面的参数 + const params: AuditCodePageInfoProps = { + Schema: "syntaxflow", + Location: record.Name, + Path: `/` + } + emiter.emit( + "openPage", + JSON.stringify({ + route: YakitRoute.YakRunner_Audit_Code, + params + }) + ) + } else { + onClose && onClose() + emiter.emit("onCodeAuditOpenAuditTree", record.Name) + } + }} + /> + + + } + onClick={() => { + const m = showYakitModal({ + title: "编辑", + width: 448, + type: "white", + footer: null, + centered: true, + content: ( + m.destroy()} + /> + ) + }) + }} + /> + + + + onDelete({ + Filter: { + Ids: [parseInt(record.Id + "")] + } + }) + } + > + } /> + +
+ ) + } + } + ] + + const loadMoreData = useMemoizedFn(() => { + if (data.length > 0) { + afterId.current = data[data.length - 1].Id + update() + } + }) + + return ( +
event.stopPropagation()} + > +
+
+
已编译项目
+
+
Total
+
{total}
+
+
Selected
-
{selected.length}
-
*/} +
{selectedRowKeys.length}
-
- } - placeholder='请输入关键词搜索' - size='small' - value={search} - onChange={(e) => { - setSearch(e.target.value) - }} - /> - } onClick={() => emiter.emit("onExecuteAuditModal")}> - 添加项目 +
+
+ } + placeholder='请输入关键词搜索' + value={params.Keyword} + onChange={(e) => { + setParams({...params, Keyword: e.target.value}) + }} + onSearch={() => { + update(true) + }} + /> + 0 ? "确定删除勾选数据吗?" : "确定清空列表数据吗?"} + onConfirm={() => { + if (selectedRowKeys.length === 0) { + onDelete({ + DeleteAll: true + }) + } else { + onDelete({ + Filter: { + Ids: selectedRowKeys.map((item) => parseInt(item + "")) + } + }) + } + }} + placement='bottomRight' + > + }> + {selectedRowKeys.length > 0 ? "删除" : "清空"} -
+ + } + onClick={() => { + if (onExecuteAudit) { + onExecuteAudit() + return + } + onClose && onClose() + emiter.emit("onExecuteAuditModal") + }} + > + 添加项目 + + {onClose && } onClick={onClose} />}
+
-
-
-
项目名称
-
存储路径
-
编译时间
-
操作
-
-
- {aduitData && - aduitData.Resources.map((item, index) => { - const obj = getAuditPath(item) - return ( -
-
- {item.ResourceName} - {obj.description && ( - - - - )} -
-
{obj.path}
-
{obj.time}
-
- - } - onClick={() => { - // addToTab(YakitRoute.YakRunner_Code_Scan) - emiter.emit( - "openPage", - JSON.stringify({ - route: YakitRoute.YakRunner_Code_Scan, - params: { - projectName: item.ResourceName - } - }) - ) - }} - /> - - - {/* 此处的Tooltip会导致页面抖动(待处理) */} - - } - onClick={() => { - emiter.emit("onCodeAuditOpenAuditTree", item.ResourceName) - }} - /> - - - onDelete(item.Path)} - > - } /> - -
-
- ) - })} -
-
+
+ + className={styles["audit-virtual-list"]} + loading={loading} + refresh={refresh} + hasMore={hasMore} + columns={columns} + data={data} + page={pagination.Page} + loadMoreData={loadMoreData} + renderKey='Id' + rowSelection={{ + isAll: isAllSelect, + type: "checkbox", + selectedRowKeys, + onSelectAll: onSelectAll, + onChangeCheckboxSingle: onSelectChange + }} + />
- + + update(true)} + warrpId={warrpId || document.getElementById("audit-history-table")} + /> +
) }) diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCodeType.d.ts b/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCodeType.d.ts index 9a1497b9ef..c072c5d657 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCodeType.d.ts +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/AuditCode/AuditCodeType.d.ts @@ -6,7 +6,9 @@ export interface YakURLKVPair { Value: string } -export interface AuditCodeProps {} +export interface AuditCodeProps { + setOnlyFileTree: (v: boolean) => void +} export interface AuditTreeProps { data: AuditNodeProps[] @@ -71,6 +73,12 @@ export interface AuditNodeProps { ResourceType: string VerboseType: string Size: number + + // 请求Query + query?: { + Key: string + Value: number + }[] } export interface AuditYakUrlProps { @@ -82,3 +90,72 @@ export interface AuditYakUrlProps { ProgramName?: string Query?: {Key: string; Value: number}[] } + +export interface AuditMainItemFormProps {} + +export interface AuditModalFormProps { + onCancel: () => void + // 拆分后不在有默认值 + isInitDefault?: boolean + isExecuting: boolean + onStartAudit: (path: string, v: DebugPluginRequest) => void +} + +export interface AuditModalFormModalProps { + onCancel: () => void + onSuccee: (path: string) => void + isInitDefault?: boolean + title?: string + // 绑定容器 + warrpId?: HTMLElement | null +} + +export interface AfreshAuditModalProps { + afreshName?: string + setAfreshName: (v?: string) => void + onSuccee: () => void + // 绑定容器 + warrpId?: HTMLElement | null +} + +export interface QuerySSAProgramsProps { + ProgramNames?: string[] + Languages?: string[] + Ids?: number[] + BeforeUpdatedAt?: number + AfterUpdatedAt?: number + Keyword?: string + AfterID?: number + BeforeID?: number +} + +export interface SSAProgramResponse { + CreateAt: number + UpdateAt: number + Name: string + Description: string + Dbpath: string + Language: string + EngineVersion: string + Recompile: boolean + HighRiskNumber: number + CriticalRiskNumber: number + WarnRiskNumber: number + LowRiskNumber: number + Id: number +} + +export interface AuditHistoryTableProps { + pageType: "aucitCode" | "projectManager" + onClose?: () => void + onExecuteAudit?: () => void + refresh?: boolean + setRefresh?: (v: boolean) => void + warrpId?: HTMLElement | null +} + +export interface ProjectManagerEditFormProps { + record: SSAProgramResponse + setData: React.Dispatch> + onClose: () => void +} diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetails.module.scss b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetails.module.scss index 72b0d60f1a..4694d9301e 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetails.module.scss +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetails.module.scss @@ -99,3 +99,10 @@ color: var(--yakit-danger-4) !important; } } + +.no-audit{ + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetails.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetails.tsx index 837a7054dc..372d7ecc2b 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetails.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetails.tsx @@ -10,21 +10,27 @@ import classNames from "classnames" import {BottomEditorDetailsProps, JumpToEditorProps, OutputInfoProps, ShowItemType} from "./BottomEditorDetailsType" import {OutlineCogIcon, OutlineExitIcon, OutlineTrashIcon, OutlineXIcon} from "@/assets/icon/outline" import {YakitButton} from "@/components/yakitUI/YakitButton/YakitButton" -import {SyntaxCheckList} from "./SyntaxCheckList/SyntaxCheckList" import useStore from "../hooks/useStore" import emiter from "@/utils/eventBus/eventBus" -import {Selection} from "../RunnerTabs/RunnerTabsType" -import { HelpInfoList } from "@/pages/yakRunner/CollapseList/CollapseList" +import {PaperAirplaneIcon} from "@/assets/newIcon" +import {RuleEditorBox} from "./RuleEditorBox/RuleEditorBox" +import useDispatcher from "../hooks/useDispatcher" +import {HoleBugDetail} from "@/pages/yakRunnerCodeScan/AuditCodeDetailDrawer/AuditCodeDetailDrawer" +import {YakitEmpty} from "@/components/yakitUI/YakitEmpty/YakitEmpty" const {ipcRenderer} = window.require("electron") // 编辑器区域 展示详情(输出/语法检查/终端/帮助信息) export const BottomEditorDetails: React.FC = (props) => { const {isShowEditorDetails, setEditorDetails, showItem, setShowItem} = props - - const {activeFile, fileTree} = useStore() + const {projectName, auditExecuting} = useStore() + const {setAuditRule} = useDispatcher() // 不再重新加载的元素 const [showType, setShowType] = useState([]) + // monaco输入内容 + const [ruleEditor, setRuleEditor] = useState("") + // 展示所需的BugHash + const [bugHash, setBugHash] = useState() // 数组去重 const filterItem = (arr) => arr.filter((item, index) => arr.indexOf(item) === index) @@ -36,24 +42,6 @@ export const BottomEditorDetails: React.FC = (props) = } }, [showItem, isShowEditorDetails]) - const syntaxCheckData = useMemo(() => { - if (activeFile?.syntaxCheck) { - return activeFile.syntaxCheck - } - return [] - }, [activeFile?.syntaxCheck]) - - // 跳转至编辑器并选中 - const onJumpToEditor = useMemoizedFn((selections: Selection) => { - if (activeFile?.path) { - const obj: JumpToEditorProps = { - selections, - id: activeFile?.path || "" - } - emiter.emit("onCodeAuditJumpEditorDetail", JSON.stringify(obj)) - } - }) - const onOpenBottomDetailFun = useMemoizedFn((v: string) => { try { const {type}: {type: ShowItemType} = JSON.parse(v) @@ -62,14 +50,28 @@ export const BottomEditorDetails: React.FC = (props) = } catch (error) {} }) + const onCodeAuditOpenBugDetailFun = useMemoizedFn((hash: string) => { + setBugHash(hash) + }) + useEffect(() => { emiter.on("onCodeAuditOpenBottomDetail", onOpenBottomDetailFun) + // 打开编译BUG详情 + emiter.on("onCodeAuditOpenBugDetail", onCodeAuditOpenBugDetailFun) return () => { emiter.off("onCodeAuditOpenBottomDetail", onOpenBottomDetailFun) + emiter.off("onCodeAuditOpenBugDetail", onCodeAuditOpenBugDetailFun) } }, []) + const onAuditRuleSubmit = useMemoizedFn(() => { + setAuditRule && setAuditRule(ruleEditor) + emiter.emit("onAuditRuleSubmit", ruleEditor) + }) + const onStopAuditRule = useMemoizedFn(() => { + emiter.emit("onStopAuditRule") + }) return (
@@ -77,25 +79,45 @@ export const BottomEditorDetails: React.FC = (props) =
setShowItem("syntaxCheck")} + onClick={() => setShowItem("ruleEditor")} > -
语法检查
- {activeFile &&
{syntaxCheckData.length}
} +
规则编写
setShowItem("helpInfo")} + onClick={() => setShowItem("holeDetail")} > -
帮助信息
+
漏洞详情
+ {showItem === "ruleEditor" && ( + <> + {auditExecuting ? ( + } + onClick={onStopAuditRule} + > + 暂停执行 + + ) : ( + } + onClick={onAuditRuleSubmit} + disabled={!projectName || ruleEditor.length === 0} + > + 开始审计 + + )} + + )} } @@ -106,30 +128,27 @@ export const BottomEditorDetails: React.FC = (props) =
- {showType.includes("syntaxCheck") && ( + {showType.includes("ruleEditor") && (
- {activeFile ? ( - - ) : ( -
请选中具体文件查看语法检查信息
- )} +
)} - {/* 帮助信息只有yak有 */} - {showType.includes("helpInfo") && ( + {showType.includes("holeDetail") && (
- {activeFile?.language === "yak" ? ( - + {bugHash ? ( + ) : ( -
请选中yak文件查看帮助信息
+
+ +
)}
)} diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetailsType.d.ts b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetailsType.d.ts index d2e3e4b2e9..4c1e30d782 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetailsType.d.ts +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/BottomEditorDetailsType.d.ts @@ -6,7 +6,7 @@ export interface BottomEditorDetailsProps { setEditorDetails:(v:boolean)=>void } -export type ShowItemType = "syntaxCheck" | "helpInfo" +export type ShowItemType = "ruleEditor" | "holeDetail" export interface JumpToEditorProps { isSelect?: boolean diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/RuleEditorBox/RuleEditorBox.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/RuleEditorBox/RuleEditorBox.tsx new file mode 100644 index 0000000000..32a5749fc6 --- /dev/null +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/RuleEditorBox/RuleEditorBox.tsx @@ -0,0 +1,78 @@ +import React, {useEffect, useRef, useState} from "react" +import {useGetState, useMemoizedFn, useUpdateEffect} from "ahooks" +import {YakitEditor} from "@/components/yakitUI/YakitEditor/YakitEditor" +import {SyntaxFlowMonacoSpec} from "@/utils/monacoSpec/syntaxflowEditor" +import useStore from "../../hooks/useStore" +import {apiFetchQuerySyntaxFlowResult} from "@/pages/yakRunnerCodeScan/utils" +import {QuerySyntaxFlowResultResponse} from "@/pages/yakRunnerCodeScan/YakRunnerCodeScanType" +const {ipcRenderer} = window.require("electron") +export interface RuleEditorBoxProps { + ruleEditor: string + setRuleEditor: (value: string) => void + disabled?: boolean +} +export const RuleEditorBox: React.FC = (props) => { + const {ruleEditor, setRuleEditor, disabled} = props + const {projectName, pageInfo} = useStore() + + // 获取文本域输入框 + const onGrpcSetTextArea = useMemoizedFn((arr: {Key: string; Value: number}[]) => { + let resultId: number | null = null + arr.forEach((item) => { + if (item.Key === "result_id") { + resultId = item.Value + } + }) + if (resultId) { + const Pagination = { + Page: 1, + Limit: 10, + Order: "desc", + OrderBy: "Id" + } + apiFetchQuerySyntaxFlowResult({ + Pagination, + Filter: { + TaskIDs: [], + ResultIDs: [resultId], + RuleNames: [], + ProgramNames: [], + Keyword: "", + OnlyRisk: false + } + }) + .then((rsp: QuerySyntaxFlowResultResponse) => { + const resData = rsp?.Results || [] + if (resData.length > 0) { + setRuleEditor(resData[0].RuleContent) + } + }) + .catch(() => { + setRuleEditor("") + }) + } + }) + + useEffect(() => { + if (pageInfo && pageInfo.Query) { + onGrpcSetTextArea(pageInfo.Query) + } else { + setRuleEditor("") + } + }, [pageInfo?.Query]) + + useUpdateEffect(() => { + setRuleEditor("") + }, [projectName]) + + return ( + { + setRuleEditor(content) + }} + disabled={disabled} + /> + ) +} diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.module.scss b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.module.scss deleted file mode 100644 index cae20ea36f..0000000000 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.module.scss +++ /dev/null @@ -1,48 +0,0 @@ -.syntax-check-list { - height: 100%; - overflow: auto; - .syntax-check-item { - display: flex; - flex-direction: row; - padding: 3px 12px 3px 8px; - align-items: center; - gap: 8px; - cursor: pointer; - .text { - font-size: 12px; - font-weight: 400; - line-height: 16px; - // flex: 1; - overflow: hidden; - } - .area { - color: #b4bbca; - font-size: 12px; - font-weight: 400; - line-height: 16px; - white-space: nowrap; - } - .icon-box { - display: flex; - svg { - height: 16px; - width: 16px; - } - } - .hint-icon { - color: #b4bbca; - } - .info-icon { - color: #4a94f8; - } - .warn-icon { - color: #ffb660; - } - .error-icon { - color: #f6544a; - } - &:hover { - background: #f8f8f8; - } - } -} diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.tsx deleted file mode 100644 index 9e27e239da..0000000000 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomEditorDetails/SyntaxCheckList/SyntaxCheckList.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, {useEffect, useMemo, useRef, useState} from "react" -import {} from "antd" -import {} from "@ant-design/icons" -import {useGetState, useMemoizedFn} from "ahooks" -import {NetWorkApi} from "@/services/fetch" -import {API} from "@/services/swagger/resposeType" -import styles from "./SyntaxCheckList.module.scss" -import {failed, success, warn, info} from "@/utils/notification" -import classNames from "classnames" -import useStore from "../../hooks/useStore" -import {IMonacoEditorMarker} from "@/utils/editorMarkers" -import {OutlineDeprecatedIcon} from "@/assets/icon/outline" -import {SolidExclamationIcon, SolidInformationcircleIcon, SolidXcircleIcon} from "@/assets/icon/solid" -import {Selection} from "../../RunnerTabs/RunnerTabsType" -const {ipcRenderer} = window.require("electron") -export interface SyntaxCheckListProps { - syntaxCheckData: IMonacoEditorMarker[] - onJumpToEditor: (v: Selection) => void -} -export const SyntaxCheckList: React.FC = (props) => { - const {syntaxCheckData, onJumpToEditor} = props - - const showIcon = useMemoizedFn((severity) => { - switch (severity) { - // Hint - case 1: - return ( -
- -
- ) - // Info - case 2: - return ( -
- -
- ) - // Warning - case 4: - return ( -
- -
- ) - // Error - case 8: - return ( -
- -
- ) - default: - return <> - } - }) - - return ( -
- {syntaxCheckData.map((item,index) => { - const {severity, message, startLineNumber, startColumn, endLineNumber, endColumn} = item - return ( -
{ - onJumpToEditor({ - startLineNumber, - startColumn, - endLineNumber, - endColumn - }) - }} - > - {showIcon(severity)} -
{message}
-
- [Ln {startLineNumber},Col {startColumn} - Ln {endLineNumber},Col {endColumn}] -
-
- ) - })} -
- ) -} diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomSideBar/BottomSideBar.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomSideBar/BottomSideBar.tsx index c27f44334e..891c014c2f 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomSideBar/BottomSideBar.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/BottomSideBar/BottomSideBar.tsx @@ -4,13 +4,8 @@ import {BottomSideBarProps} from "./BottomSideBarType" import classNames from "classnames" import styles from "./BottomSideBar.module.scss" import { - OutlineAnnotationIcon, - OutlineCodeIcon, - OutlineDeprecatedIcon, - OutlineExclamationIcon, - OutlineInformationcircleIcon, - OutlineStethoscopeIcon, - OutlineXcircleIcon + OutlineBugIcon, + OutlineScanRuleEditIcon, } from "@/assets/icon/outline" import useStore from "../hooks/useStore" @@ -19,33 +14,6 @@ const {ipcRenderer} = window.require("electron") export const BottomSideBar: React.FC = (props) => { const {onOpenEditorDetails} = props const {activeFile} = useStore() - const showSyntaxInfo = useMemo(() => { - let data = { - hint: 0, - info: 0, - warning: 0, - error: 0 - } - if (activeFile?.syntaxCheck) { - activeFile.syntaxCheck.forEach((item) => { - switch (item.severity) { - case 1: - data.hint += 1 - break - case 2: - data.info += 1 - break - case 4: - data.warning += 1 - break - case 8: - data.error += 1 - break - } - }) - } - return data - }, [activeFile]) const showLocationInfo = useMemo(() => { let data = { @@ -58,65 +26,28 @@ export const BottomSideBar: React.FC = (props) => { } return data }, [activeFile?.position]) + return (
{/* 语法检查|终端|帮助信息 */}
-
-
{ - onOpenEditorDetails("syntaxCheck") - }} - > - - 语法检查 -
-
{ - onOpenEditorDetails("syntaxCheck") - }} - > - - {showSyntaxInfo.error} -
-
{ - onOpenEditorDetails("syntaxCheck") - }} - > - - {showSyntaxInfo.warning} -
-
{ - onOpenEditorDetails("syntaxCheck") - }} - > - - {showSyntaxInfo.info} -
-
{ - onOpenEditorDetails("syntaxCheck") - }} - > - - {showSyntaxInfo.hint} -
+
{ + onOpenEditorDetails("ruleEditor") + }} + > + + 规则编写
{ - onOpenEditorDetails("helpInfo") + onOpenEditorDetails("holeDetail") }} > - - 帮助信息 + + 漏洞详情
diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/LeftAudit/LeftAudit.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/LeftAudit/LeftAudit.tsx index 7a8bc80325..53f8e81537 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/LeftAudit/LeftAudit.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/LeftAudit/LeftAudit.tsx @@ -1,7 +1,5 @@ -import React, {useEffect, useRef, useState} from "react" -import {} from "antd" -import {} from "@ant-design/icons" -import {useGetState} from "ahooks" +import React, {useEffect, useMemo, useRef, useState} from "react" +import {useCreation, useGetState, useSize} from "ahooks" import {NetWorkApi} from "@/services/fetch" import {API} from "@/services/swagger/resposeType" import styles from "./LeftAudit.module.scss" @@ -10,24 +8,55 @@ import classNames from "classnames" import {AuditCode} from "../AuditCode/AuditCode" import {YakitResizeBox} from "@/components/yakitUI/YakitResizeBox/YakitResizeBox" import {RunnerFileTree} from "../RunnerFileTree/RunnerFileTree" -import {AuditEmiterYakUrlProps} from "../YakRunnerAuditCodeType" +import useStore from "../hooks/useStore" const {ipcRenderer} = window.require("electron") export interface LeftAuditProps { fileTreeLoad: boolean } export const LeftAudit: React.FC = (props) => { const {fileTreeLoad} = props + const {pageInfo} = useStore() + const [isOnlyFileTree, setOnlyFileTree] = useState(true) + const ref = useRef(null) + const getContainerSize = useSize(ref) + // 抽屉展示高度 + const boxHeight = useMemo(() => { + return getContainerSize?.height || 400 + }, [getContainerSize]) + + const ResizeBoxProps = useCreation(() => { + let p = { + firstRatio: "50%", + secondRatio: "50%", + firstMinSize: 250, + secondMinSize: 180, + lineStyle: {} + } + if (isOnlyFileTree) { + p.secondRatio = "0%" + p.firstRatio = "100%" + p.secondMinSize = 0 + p.lineStyle = {display: "none"} + } + return p + }, [isOnlyFileTree]) + + // 当跳转打开时,没有则关闭 + useEffect(() => { + if (!pageInfo?.Query) { + setOnlyFileTree(true) + } + }, [pageInfo]) + return ( -
+
} - secondNode={} + firstNode={} + secondNode={} + {...ResizeBoxProps} />
) diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RightAuditDetail/RightAuditDetail.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RightAuditDetail/RightAuditDetail.tsx index b77f1d726b..22e8d48840 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RightAuditDetail/RightAuditDetail.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RightAuditDetail/RightAuditDetail.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useMemo, useRef, useState} from "react" +import React, {useEffect, useMemo, useRef, useState, WheelEvent} from "react" import classNames from "classnames" import styles from "./RightAuditDetail.module.scss" import {useMemoizedFn, useThrottleFn, useUpdate, useUpdateEffect} from "ahooks" @@ -22,7 +22,8 @@ import {clearMapGraphInfoDetail, getMapGraphInfoDetail, setMapGraphInfoDetail} f import {CollapseList} from "@/pages/yakRunner/CollapseList/CollapseList" import {AuditEmiterYakUrlProps, OpenFileByPathProps} from "../YakRunnerAuditCodeType" import {v4 as uuidv4} from "uuid" -import { JumpToEditorProps } from "../BottomEditorDetails/BottomEditorDetailsType" +import {JumpToEditorProps} from "../BottomEditorDetails/BottomEditorDetailsType" +import {YakCodemirror} from "@/components/yakCodemirror/YakCodemirror" interface AuditResultItemProps { onDetail: (data: CodeRangeProps) => void @@ -62,7 +63,21 @@ export const AuditResultItem: React.FC = (props) => { } const renderItem = (info: GraphInfoProps) => { - return
{info.source_code}
+ const filename = info.code_range.url.split("/").pop() + const {start_line, end_line, source_code_line, start_column, end_column} = info.code_range + return ( + + ) + // return
{info.source_code}
} return ( @@ -366,6 +381,15 @@ export const FlowChartBox: React.FC = (props) => { {wait: 200} ).run + const handleWheel = useMemoizedFn((event: WheelEvent) => { + if (event.deltaY > 0) { + // 最小缩放比例为0.2 + setScale((prevScale) => Math.max(0.2, prevScale - 0.2)) + } else if (event.deltaY < 0) { + setScale((prevScale) => prevScale + 0.2) + } + }) + return (
@@ -399,6 +423,7 @@ export const FlowChartBox: React.FC = (props) => { onMouseUp={handleMouseUp} onMouseMove={handleMouseMove} onMouseLeave={handleMouseUp} + onWheel={handleWheel} ref={svgBoxRef} /> {nodeId && ( @@ -427,7 +452,26 @@ export const FlowChartBox: React.FC = (props) => {
)} - {contentInfo &&
{contentInfo?.ir_code}
} + {contentInfo && ( + + )} + {/* {contentInfo &&
{contentInfo?.ir_code}
} */}
)} @@ -441,6 +485,8 @@ export interface CodeRangeProps { start_line: number end_column: number end_line: number + // 代码段启示行数 + source_code_line: number } export interface GraphInfoProps { diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTree.module.scss b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTree.module.scss index d0fb6bb525..d221358c94 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTree.module.scss +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTree.module.scss @@ -144,4 +144,4 @@ } } } -} +} \ No newline at end of file diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTree.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTree.tsx index c1ab1fa209..955319d406 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTree.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTree.tsx @@ -7,6 +7,7 @@ import { OutlinePluscircleIcon, OutlinePositionIcon, OutlineRefreshIcon, + OutlineReloadScanIcon, OutlineScanIcon, OutlineXIcon } from "@/assets/icon/outline" @@ -18,7 +19,13 @@ import classNames from "classnames" import styles from "./RunnerFileTree.module.scss" import {YakitDropdownMenu} from "@/components/yakitUI/YakitDropdownMenu/YakitDropdownMenu" import {YakitMenuItemProps, YakitMenuItemType} from "@/components/yakitUI/YakitMenu/YakitMenu" -import {getDefaultActiveFile, grpcFetchAuditTree, removeAreaFileInfo, setAreaFileActive, updateAreaFileInfo} from "../utils" +import { + getDefaultActiveFile, + grpcFetchAuditTree, + removeAreaFileInfo, + setAreaFileActive, + updateAreaFileInfo +} from "../utils" import emiter from "@/utils/eventBus/eventBus" import { @@ -42,7 +49,7 @@ import {failed, success} from "@/utils/notification" import {FileMonitorItemProps, FileMonitorProps} from "@/utils/duplex/duplex" import {Tooltip} from "antd" import {YakitDrawer} from "@/components/yakitUI/YakitDrawer/YakitDrawer" -import {AuditHistoryTable} from "../AuditCode/AuditCode" +import {AfreshAuditModal, AuditHistoryTable} from "../AuditCode/AuditCode" import {KeyToIcon} from "@/pages/yakRunner/FileTree/icon" import {OpenFileByPathProps} from "../YakRunnerAuditCodeType" @@ -51,16 +58,17 @@ import {FileTree} from "../FileTree/FileTree" import {addToTab} from "@/pages/MainTabs" import {YakitRoute} from "@/enums/yakitRoute" import {YakitSpin} from "@/components/yakitUI/YakitSpin/YakitSpin" -import { FileDetailInfo } from "../RunnerTabs/RunnerTabsType" -import { FileNodeProps, FileTreeListProps } from "../FileTree/FileTreeType" +import {FileDetailInfo} from "../RunnerTabs/RunnerTabsType" +import {FileNodeProps, FileTreeListProps} from "../FileTree/FileTreeType" const {ipcRenderer} = window.require("electron") export const RunnerFileTree: React.FC = memo((props) => { - const {fileTreeLoad} = props + const {fileTreeLoad, boxHeight} = props const {fileTree, activeFile, projectName} = useStore() const {handleFileLoadData} = useDispatcher() - + const [afreshName, setAfreshName] = useState() + const [visible, setVisible] = useState(false) const [aduitList, setAduitList] = useState<{path: string; name: string}[]>([]) // 选中的文件或文件夹 const [foucsedKey, setFoucsedKey] = React.useState("") @@ -155,6 +163,10 @@ export const RunnerFileTree: React.FC = memo((props) => { const menuData: YakitMenuItemType[] = useMemo(() => { let newMenu: YakitMenuItemType[] = [ + { + key: "codeScan", + label: "代码扫描" + }, { key: "auditCode", label: "编译项目" @@ -198,11 +210,22 @@ export const RunnerFileTree: React.FC = memo((props) => { const menuSelect = useMemoizedFn((key, keyPath: string[]) => { switch (key) { + case "codeScan": + emiter.emit( + "openPage", + JSON.stringify({ + route: YakitRoute.YakRunner_Code_Scan, + params: { + projectName + } + }) + ) + break case "auditCode": auditCode() break case "aduitAllList": - emiter.emit("onInitAuditCodePage") + setVisible(true) break default: if (keyPath.includes("auditHistory")) { @@ -239,6 +262,11 @@ export const RunnerFileTree: React.FC = memo((props) => { } }) + // 关闭抽屉 + const onCloseDrawer = useMemoizedFn(() => { + setVisible(false) + }) + return (
@@ -260,22 +288,13 @@ export const RunnerFileTree: React.FC = memo((props) => { onClick={onActiveFileScrollToFileTree} /> - + } + icon={} onClick={() => { - // addToTab(YakitRoute.YakRunner_Code_Scan) - emiter.emit( - "openPage", - JSON.stringify({ - route: YakitRoute.YakRunner_Code_Scan, - params: { - projectName - } - }) - ) + setAfreshName(projectName) }} /> @@ -321,6 +340,32 @@ export const RunnerFileTree: React.FC = memo((props) => {
+ + setVisible(false)} + warrpId={document.getElementById("audit-code")} + /> + + + { + emiter.emit("onCodeAuditRefreshTree") + }} + />
) }) diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTreeType.d.ts b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTreeType.d.ts index db385e7539..855b80b892 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTreeType.d.ts +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerFileTree/RunnerFileTreeType.d.ts @@ -1,5 +1,6 @@ export interface RunnerFileTreeProps { fileTreeLoad: boolean + boxHeight: number } export interface OpenedFileProps{ diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerTabs/RunnerTabs.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerTabs/RunnerTabs.tsx index 89f04f1857..81e1f89447 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerTabs/RunnerTabs.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerTabs/RunnerTabs.tsx @@ -7,10 +7,10 @@ import { RunnerTabBarProps, RunnerTabPaneProps, RunnerTabsProps, - YakRunnerWelcomePageProps, SplitDirectionProps, YakitRunnerSaveModalProps, - RunYakParamsProps + RunYakParamsProps, + AuditCodeWelcomePageProps } from "./RunnerTabsType" import {Droppable, Draggable} from "@hello-pangea/dnd" @@ -29,7 +29,7 @@ import { OutlineSplitScreenIcon, OutlineXIcon } from "@/assets/icon/outline" -import {SolidYakCattleNoBackColorIcon} from "@/assets/icon/colors" +import {RuleManagementAuditIcon, SolidYakCattleNoBackColorIcon} from "@/assets/icon/colors" import { YakRunnerNewFileIcon, YakRunnerOpenAuditIcon, @@ -1183,8 +1183,8 @@ const RunnerTabPane: React.FC = memo((props) => { ) }) -export const YakRunnerWelcomePage: React.FC = memo((props) => { - const {addFileTab, setShowCompileModal} = props +export const AuditCodeWelcomePage: React.FC = memo((props) => { + const {setShowCompileModal} = props const ref = useRef(null) const size = useSize(ref) const [historyList, setHistoryList] = useState([]) @@ -1216,21 +1216,6 @@ export const YakRunnerWelcomePage: React.FC = memo((p } catch (error) {} }) - // 打开文件夹 - const openFolder = useMemoizedFn(() => { - ipcRenderer - .invoke("openDialog", { - title: "请选择文件夹", - properties: ["openDirectory"] - }) - .then((data: any) => { - if (data.filePaths.length) { - let absolutePath: string = data.filePaths[0].replace(/\\/g, "\\") - emiter.emit("onOpenFileTree", absolutePath) - } - }) - }) - // 打开编译项目 const openCompileProject = useMemoizedFn(() => { setShowCompileModal(true) @@ -1240,38 +1225,14 @@ export const YakRunnerWelcomePage: React.FC = memo((p
- +
-
欢迎使用 Yak 语言
+
欢迎使用SyntaxFlow代码审计
快捷创建
-
-
- - 新建文件 -
- -
-
-
- - 打开文件 -
- -
-
-
- - 打开文件夹 -
- -
= memo((p
+
+
+ + 打开已有项目 +
+ +
diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerTabs/RunnerTabsType.d.ts b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerTabs/RunnerTabsType.d.ts index 14bfd3381d..9cfe426bb6 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerTabs/RunnerTabsType.d.ts +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/RunnerTabs/RunnerTabsType.d.ts @@ -92,8 +92,7 @@ export interface RunnerTabPaneProps { tabsId: string } -export interface YakRunnerWelcomePageProps { - addFileTab: () => void +export interface AuditCodeWelcomePageProps { setShowCompileModal: (v: boolean) => void } diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/YakRunnerAuditCode.module.scss b/app/renderer/src/main/src/pages/yakRunnerAuditCode/YakRunnerAuditCode.module.scss index 5238125068..d351d431eb 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/YakRunnerAuditCode.module.scss +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/YakRunnerAuditCode.module.scss @@ -136,6 +136,8 @@ } .log-info { margin-top: 16px; + max-height: 400px; + overflow: auto; .log-item { color: #85899e; font-size: 14px; diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/YakRunnerAuditCode.tsx b/app/renderer/src/main/src/pages/yakRunnerAuditCode/YakRunnerAuditCode.tsx index da94a4fa28..525e312201 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/YakRunnerAuditCode.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/YakRunnerAuditCode.tsx @@ -9,10 +9,22 @@ import { YakRunnerHistoryProps } from "./YakRunnerAuditCodeType" import {Divider, Progress, Tooltip} from "antd" -import {} from "@ant-design/icons" -import {AuditHistoryTable, AuditModalForm, AuditModalFormModal} from "./AuditCode/AuditCode" +import {AuditModalForm, AuditModalFormModal} from "./AuditCode/AuditCode" import {useMemoizedFn} from "ahooks" -import {addAreaFileInfo, getCodeByPath, getCodeSizeByPath, getNameByPath, grpcFetchAuditTree, judgeAreaExistAuditPath, judgeAreaExistFilePath, monacaLanguageType, removeAreaFilesInfo, setAreaFileActive, setYakRunnerHistory, updateAreaFileInfo} from "./utils" +import { + addAreaFileInfo, + getCodeByPath, + getCodeSizeByPath, + getNameByPath, + grpcFetchAuditTree, + judgeAreaExistAuditPath, + judgeAreaExistFilePath, + monacaLanguageType, + removeAreaFilesInfo, + setAreaFileActive, + setYakRunnerHistory, + updateAreaFileInfo +} from "./utils" import styles from "./YakRunnerAuditCode.module.scss" import {YakitEmpty} from "@/components/yakitUI/YakitEmpty/YakitEmpty" import {YakitButton} from "@/components/yakitUI/YakitButton/YakitButton" @@ -44,35 +56,43 @@ import {SplitView} from "../yakRunner/SplitView/SplitView" import {LeftAudit} from "./LeftAudit/LeftAudit" import {BottomEditorDetails} from "./BottomEditorDetails/BottomEditorDetails" import {ShowItemType} from "./BottomEditorDetails/BottomEditorDetailsType" -import {RunnerTabs} from "./RunnerTabs/RunnerTabs" +import {AuditCodeWelcomePage, RunnerTabs} from "./RunnerTabs/RunnerTabs" import {BottomSideBar} from "./BottomSideBar/BottomSideBar" import {SolidDocumentdownloadIcon} from "@/assets/icon/solid" import {StreamResult} from "@/hook/useHoldGRPCStream/useHoldGRPCStreamType" -import {RightBugDetail} from "../yakRunnerCodeScan/AuditCodeDetailDrawer/AuditCodeDetailDrawer" import {AuditCodePageInfoProps} from "@/store/pageInfo" -import { FileDetailInfo } from "./RunnerTabs/RunnerTabsType" -import { FileNodeMapProps, FileTreeListProps } from "./FileTree/FileTreeType" +import {FileDetailInfo} from "./RunnerTabs/RunnerTabsType" +import {FileNodeMapProps, FileTreeListProps} from "./FileTree/FileTreeType" +import {showYakitModal} from "@/components/yakitUI/YakitModal/YakitModalConfirm" +import {YakitHint} from "@/components/yakitUI/YakitHint/YakitHint" const {ipcRenderer} = window.require("electron") export const YakRunnerAuditCode: React.FC = (props) => { const {auditCodePageInfo} = props // 页面数据 const [pageInfo, setPageInfo] = useState(auditCodePageInfo) - const [loading, setLoading] = useState(false) - // 是否展示已编译项目列表 - const [isShowAuditList, setShowAuditList] = useState(false) /** ---------- 文件树 ---------- */ const [fileTree, setFileTree] = useState([]) /** ---------- 审计树 ---------- */ const [projectName, setProjectName] = useState(auditCodePageInfo?.Location) const [areaInfo, setAreaInfo] = useState([]) const [activeFile, setActiveFile] = useState() + /** ---------- 审计规则 ---------- */ + const [auditRule, setAuditRule] = useState("") + /** ---------- 审计运行状态 ---------- */ + const [auditExecuting, setAuditExecuting] = useState(false) const [isShowCompileModal, setShowCompileModal] = useState(false) + const [isShowModal, setShowModal] = useState(false) const setAuditCodePageInfo = useMemoizedFn((auditCodePageInfo: string) => { try { + if (auditExecuting) { + setShowModal(true) + return + } const newPageInfo: AuditCodePageInfoProps = JSON.parse(auditCodePageInfo) setPageInfo(newPageInfo) + setAuditRule("") const {Location} = newPageInfo setProjectName(Location) onInitTreeFun(`/${Location}`) @@ -342,6 +362,7 @@ export const YakRunnerAuditCode: React.FC = (props) => setProjectName(undefined) setAreaInfo([]) setActiveFile(undefined) + setAuditRule("") }) useEffect(() => { @@ -351,7 +372,7 @@ export const YakRunnerAuditCode: React.FC = (props) => emiter.on("onCodeAuditRefreshTree", onCodeAuditRefreshTreeFun) // 通过路径打开文件 emiter.on("onCodeAuditOpenFileByPath", onOpenFileByPathFun) - // 重置整个页面 + // 重置整个页面(暂时弃用) emiter.on("onInitAuditCodePage", onInitAuditCodePageFun) return () => { emiter.off("onCodeAuditOpenAuditTree", onOpenAuditTreeFun) @@ -363,7 +384,7 @@ export const YakRunnerAuditCode: React.FC = (props) => const [isShowEditorDetails, setEditorDetails] = useState(false) // 当前展示项 - const [showItem, setShowItem] = useState("syntaxCheck") + const [showItem, setShowItem] = useState("ruleEditor") // 最后焦点聚集编辑器输出 const onFixedEditorDetails = useMemoizedFn((element: React.ReactNode): React.ReactNode => { // 后续此处还需要传入焦点代码/路径等信息 @@ -500,11 +521,7 @@ export const YakRunnerAuditCode: React.FC = (props) => // 布局处理 const onChangeArea = useMemoizedFn(() => { if (areaInfo.length === 0) { - return ( -
- -
- ) + return } return ( @@ -567,25 +584,6 @@ export const YakRunnerAuditCode: React.FC = (props) => } }, []) - const getAduitList = useMemoizedFn(async () => { - try { - setLoading(true) - const {res} = await grpcFetchAuditTree("/") - if (res.Resources.length > 0) { - setShowAuditList(true) - } - setLoading(false) - } catch (error) { - setLoading(false) - } - }) - - useEffect(() => { - if(!projectName){ - getAduitList() - } - }, [projectName]) - // 加载下一层 const handleFileLoadData = useMemoizedFn((path: string) => { return new Promise((resolve, reject) => { @@ -623,9 +621,11 @@ export const YakRunnerAuditCode: React.FC = (props) => fileTree: fileTree, projectName: projectName, areaInfo: areaInfo, - activeFile: activeFile + activeFile: activeFile, + auditRule: auditRule, + auditExecuting: auditExecuting } - }, [pageInfo, fileTree, projectName, areaInfo, activeFile]) + }, [pageInfo, fileTree, projectName, areaInfo, activeFile, auditRule, auditExecuting]) const dispatcher: YakRunnerContextDispatcher = useMemo(() => { return { @@ -634,23 +634,17 @@ export const YakRunnerAuditCode: React.FC = (props) => setProjectName: setProjectName, handleFileLoadData: handleFileLoadData, setAreaInfo: setAreaInfo, - setActiveFile: setActiveFile + setActiveFile: setActiveFile, + setAuditRule: setAuditRule, + setAuditExecuting: setAuditExecuting } }, []) - const [bugHash, setBugHash] = useState() const [auditRightParams, setAuditRightParams] = useState() - const onOpenAuditRightBugDetailFun = useMemoizedFn((hash: string) => { - setBugHash(hash) - setAuditRightParams(undefined) - setShowAuditDetail(true) - }) - const onOpenAuditRightDetailFun = useMemoizedFn((value: string) => { try { const data: AuditEmiterYakUrlProps = JSON.parse(value) - setBugHash(undefined) setAuditRightParams(data) setShowAuditDetail(true) emiter.emit("onCodeAuditRefreshAuditDetail") @@ -660,11 +654,8 @@ export const YakRunnerAuditCode: React.FC = (props) => useEffect(() => { // 正常打开编译右侧详情 emiter.on("onCodeAuditOpenRightDetail", onOpenAuditRightDetailFun) - // 打开编译右侧BUG详情 - emiter.on("onCodeAuditOpenRightBugDetail", onOpenAuditRightBugDetailFun) return () => { emiter.off("onCodeAuditOpenRightDetail", onOpenAuditRightDetailFun) - emiter.off("onCodeAuditOpenRightBugDetail", onOpenAuditRightBugDetailFun) } }, []) @@ -686,8 +677,7 @@ export const YakRunnerAuditCode: React.FC = (props) => return ( -
- {projectName ? ( +
= (props) =>
} secondNode={ - <> - {bugHash ? ( - - ) : ( - - )} - + } /> } @@ -741,46 +725,15 @@ export const YakRunnerAuditCode: React.FC = (props) =>
- ) : ( - <> - {isShowAuditList ? ( - - ) : ( - -
- - } - onClick={() => onOpenAuditModalFun()} - > - 编译项目 - - } - onClick={getAduitList} - > - 刷新 - -
- } - /> -
- - )} - - )} - - {isShowCompileModal && ( - } + setShowModal(false)} /> - )}
diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/hooks/YakRunnerContext.ts b/app/renderer/src/main/src/pages/yakRunnerAuditCode/hooks/YakRunnerContext.ts index f81fbed446..a40210ed5d 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/hooks/YakRunnerContext.ts +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/hooks/YakRunnerContext.ts @@ -10,6 +10,8 @@ export interface YakRunnerContextStore { projectName: string | undefined areaInfo: AreaInfoProps[] activeFile: FileDetailInfo | undefined + auditRule: string, + auditExecuting: boolean } export interface YakRunnerContextDispatcher { @@ -19,6 +21,8 @@ export interface YakRunnerContextDispatcher { handleFileLoadData?: (path: string) => Promise setAreaInfo?: Dispatch> setActiveFile?: Dispatch> + setAuditRule?: Dispatch> + setAuditExecuting?: Dispatch> } export interface YakRunnerContextValue { @@ -32,7 +36,9 @@ export default createContext({ fileTree: [], projectName: undefined, areaInfo: [], - activeFile: undefined + activeFile: undefined, + auditRule: "", + auditExecuting: false }, dispatcher: { setPageInfo: undefined, @@ -40,6 +46,8 @@ export default createContext({ setProjectName: undefined, handleFileLoadData: undefined, setAreaInfo: undefined, - setActiveFile: undefined + setActiveFile: undefined, + setAuditRule: undefined, + setAuditExecuting: undefined } }) diff --git a/app/renderer/src/main/src/pages/yakRunnerAuditCode/utils.ts b/app/renderer/src/main/src/pages/yakRunnerAuditCode/utils.ts index 10be503d0e..63fc2b68b1 100644 --- a/app/renderer/src/main/src/pages/yakRunnerAuditCode/utils.ts +++ b/app/renderer/src/main/src/pages/yakRunnerAuditCode/utils.ts @@ -11,10 +11,14 @@ import {randomString} from "@/utils/randomUtil" import {StringToUint8Array, Uint8ArrayToString} from "@/utils/str" import {FileDetailInfo, OptionalFileDetailInfo} from "./RunnerTabs/RunnerTabsType" import {v4 as uuidv4} from "uuid" -import { ConvertYakStaticAnalyzeErrorToMarker, IMonacoEditorMarker, YakStaticAnalyzeErrorResult } from "@/utils/editorMarkers" -import { FileNodeMapProps, FileNodeProps } from "./FileTree/FileTreeType" -import { SyntaxFlowMonacoSpec } from "@/utils/monacoSpec/syntaxflowEditor" -import { YaklangMonacoSpec } from "@/utils/monacoSpec/yakEditor" +import { + ConvertYakStaticAnalyzeErrorToMarker, + IMonacoEditorMarker, + YakStaticAnalyzeErrorResult +} from "@/utils/editorMarkers" +import {FileNodeMapProps, FileNodeProps} from "./FileTree/FileTreeType" +import {SyntaxFlowMonacoSpec} from "@/utils/monacoSpec/syntaxflowEditor" +import {YaklangMonacoSpec} from "@/utils/monacoSpec/yakEditor" const {ipcRenderer} = window.require("electron") @@ -58,7 +62,6 @@ export const grpcFetchAuditTree: (path: string) => Promise<{res: RequestYakURLRe } try { const res: RequestYakURLResponse = await ipcRenderer.invoke("RequestYakURL", params) - // console.log("审计树获取---", params, res) const data: FileNodeMapProps[] = initFileTreeData(res, path) resolve({res, data}) } catch (error) { @@ -336,7 +339,6 @@ export const judgeAreaExistFilePath = (areaInfo: AreaInfoProps[], path: string): }) } - /** * @name 新增分栏数据里某个节点的file数据 */ @@ -718,9 +720,9 @@ export const monacaLanguageType = (suffix?: string) => { switch (suffix) { case "yak": return YaklangMonacoSpec - case "sf": + case "sf": return SyntaxFlowMonacoSpec default: return undefined } -} \ No newline at end of file +} diff --git a/app/renderer/src/main/src/pages/yakRunnerCodeScan/AuditCodeDetailDrawer/AuditCodeDetailDrawer.tsx b/app/renderer/src/main/src/pages/yakRunnerCodeScan/AuditCodeDetailDrawer/AuditCodeDetailDrawer.tsx index c9653bc971..30a2baed7a 100644 --- a/app/renderer/src/main/src/pages/yakRunnerCodeScan/AuditCodeDetailDrawer/AuditCodeDetailDrawer.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerCodeScan/AuditCodeDetailDrawer/AuditCodeDetailDrawer.tsx @@ -1,6 +1,4 @@ import React, {useEffect, useMemo, useRef, useState} from "react" -import {} from "antd" -import {} from "@ant-design/icons" import {useGetState, useMap, useMemoizedFn} from "ahooks" import {NetWorkApi} from "@/services/fetch" import {API} from "@/services/swagger/resposeType" @@ -373,7 +371,7 @@ export const AuditCodeDetailDrawer: React.FC = (prop {isShowAuditDetail ? ( <> {bugId && bugHash ? ( - + ) : ( = (prop ) } -interface RightBugDetailProps { +interface HoleBugDetailProps { bugHash: string } -export const RightBugDetail: React.FC = React.memo((props) => { +export const HoleBugDetail: React.FC = React.memo((props) => { const {bugHash} = props const [info, setInfo] = useState() useEffect(() => { diff --git a/app/renderer/src/main/src/pages/yakRunnerCodeScan/CodeScanResultTable/CodeScanResultTable.tsx b/app/renderer/src/main/src/pages/yakRunnerCodeScan/CodeScanResultTable/CodeScanResultTable.tsx index e3c889dd9f..c1595b6c19 100644 --- a/app/renderer/src/main/src/pages/yakRunnerCodeScan/CodeScanResultTable/CodeScanResultTable.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerCodeScan/CodeScanResultTable/CodeScanResultTable.tsx @@ -1,6 +1,5 @@ import React, {Ref, useEffect, useRef, useState} from "react" import {Divider, Tooltip} from "antd" -import {} from "@ant-design/icons" import {useDebounceFn, useGetState, useInViewport, useMemoizedFn, useSize, useUpdateEffect} from "ahooks" import {NetWorkApi} from "@/services/fetch" import {API} from "@/services/swagger/resposeType" diff --git a/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScan.module.scss b/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScan.module.scss index 3811307ebd..78c713e92a 100644 --- a/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScan.module.scss +++ b/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScan.module.scss @@ -4,6 +4,15 @@ flex: 1 1; height: 100%; overflow: hidden; + position: relative; + :global { + .ant-modal-mask { + position: absolute; + } + .ant-modal-wrap { + position: absolute; + } + } .left-wrapper { display: flex; flex-direction: column; @@ -191,12 +200,18 @@ .code-scan-executor-body { padding: 0 12px 16px; } + .code-scan-executor-btn { + .progress-show { + display: flex; + align-items: center; + gap: 2px; + } + } } .code-scan-execute-form-wrapper { - display: grid; - grid-template-rows: 1fr; - transition: all 0.3s ease; + display: flex; + flex-direction: column; padding-top: 16px; :global { .ant-form { @@ -209,8 +224,9 @@ } } .code-scan-execute-form-wrapper-hidden { - grid-template-rows: 0fr; + height: 0px; padding-top: 0; + overflow: hidden; } .code-scan-execute-log-wrapper { @@ -324,3 +340,61 @@ } } } + +.code-scan-form { + :global { + .json-schema-form { + .ant-form-item { + display: flex; + flex-direction: row; + .ant-form-item-label { + flex: 0 0 25%; + text-align: right; + label::after { + content: ":"; + position: relative; + top: -0.5px; + margin: 0 8px 0 2px; + z-index: 1; + display: inline-flex; + align-items: center; + } + } + .ant-form-item-label + .ant-form-item-control { + flex: 0 0 50%; + } + + .ant-form-item-label { + // 仅在相邻元素不是 .ant-form-item-control 时应用样式 + &:not(:has(+ .ant-form-item-control)) { + display: none; + width: 100%; + padding: 0; + margin-bottom: 20px; + text-align: left; + border: 0; + border-bottom: 1px solid #e5e5e5; + label { + font-size: 21px !important; + color: #333; + } + label::after { + content: ""; + position: relative; + top: -0.5px; + margin: 0 8px 0 2px; + z-index: 1; + display: inline-flex; + align-items: center; + } + } + } + } + + // 需询问后端是否可从schema中移除 + #root_auth__error,#root_proxy__error { + display: none; + } + } + } +} diff --git a/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScan.tsx b/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScan.tsx index d77be81066..7f6274f1a2 100644 --- a/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScan.tsx +++ b/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScan.tsx @@ -1,9 +1,12 @@ import React, {forwardRef, useEffect, useImperativeHandle, useMemo, useReducer, useRef, useState} from "react" import { CodeScaMainExecuteContentProps, + CodeScanAuditExecuteFormProps, + CodeScanAuditExecuteRefProps, CodeScanByGroupProps, CodeScanExecuteContentProps, CodeScanExecuteContentRefProps, + CodeScanExecuteExtraParamsDrawerProps, CodeScanGroupByKeyWordItemProps, CodeScanGroupByKeyWordProps, FlowRuleDetailsListItemProps, @@ -16,9 +19,16 @@ import { SyntaxFlowScanResponse, YakRunnerCodeScanProps } from "./YakRunnerCodeScanType" -import {Divider, Form, Tooltip} from "antd" -import {} from "@ant-design/icons" -import {useControllableValue, useCreation, useDebounceFn, useGetState, useInViewport, useMemoizedFn} from "ahooks" +import {Col, Divider, Form, Row, Tooltip} from "antd" +import { + useControllableValue, + useCreation, + useDebounceFn, + useGetState, + useInViewport, + useMemoizedFn, + useUpdateEffect +} from "ahooks" import {NetWorkApi} from "@/services/fetch" import {API} from "@/services/swagger/resposeType" import styles from "./YakRunnerCodeScan.module.scss" @@ -40,10 +50,13 @@ import {YakitCheckbox} from "@/components/yakitUI/YakitCheckbox/YakitCheckbox" import {YakitAutoCompleteRefProps} from "@/components/yakitUI/YakitAutoComplete/YakitAutoCompleteType" import {RemoteGV} from "@/yakitGV" import {RollingLoadList} from "@/components/RollingLoadList/RollingLoadList" -import {GroupCount} from "../invoker/schema" +import {YakScript} from "../invoker/schema" import {YakitSpin} from "@/components/yakitUI/YakitSpin/YakitSpin" import {ExpandAndRetract} from "../plugins/operator/expandAndRetract/ExpandAndRetract" -import {PluginExecuteProgress} from "../plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard" +import { + ExecuteEnterNodeByPluginParams, + PluginExecuteProgress +} from "../plugins/operator/localPluginExecuteDetailHeard/LocalPluginExecuteDetailHeard" import useHoldBatchGRPCStream from "@/hook/useHoldBatchGRPCStream/useHoldBatchGRPCStream" import {randomString} from "@/utils/randomUtil" import {YakitSelect} from "@/components/yakitUI/YakitSelect/YakitSelect" @@ -61,13 +74,20 @@ import {CodeScanPageInfoProps, PageNodeItemProps, usePageInfo} from "@/store/pag import {shallow} from "zustand/shallow" import {defaultCodeScanPageInfo} from "@/defaultConstants/CodeScan" import {Paging} from "@/utils/yakQueryHTTPFlow" -import useHoldGRPCStream, { convertCardInfo } from "@/hook/useHoldGRPCStream/useHoldGRPCStream" -import { HoldGRPCStreamProps, StreamResult } from "@/hook/useHoldGRPCStream/useHoldGRPCStreamType" +import useHoldGRPCStream, {convertCardInfo} from "@/hook/useHoldGRPCStream/useHoldGRPCStream" +import {HoldGRPCStreamProps, StreamResult} from "@/hook/useHoldGRPCStream/useHoldGRPCStreamType" import {isCommunityEdition} from "@/utils/envfile" import {WaterMark} from "@ant-design/pro-layout" -import {AuditModalFormModal} from "../yakRunnerAuditCode/AuditCode/AuditCode" import {PluginExecuteResult} from "../plugins/operator/pluginExecuteResult/PluginExecuteResult" -import { v4 as uuidv4 } from "uuid" +import {v4 as uuidv4} from "uuid" +import {grpcFetchLocalPluginDetail} from "../pluginHub/utils/grpc" +import {YakitDrawer} from "@/components/yakitUI/YakitDrawer/YakitDrawer" +import {ExtraParamsNodeByType} from "../plugins/operator/localPluginExecuteDetailHeard/PluginExecuteExtraParams" +import {getYakExecutorParam, ParamsToGroupByGroupName} from "../plugins/editDetails/utils" +import {apiCancelDebugPlugin, apiDebugPlugin, DebugPluginRequest} from "../plugins/utils" +import {HTTPRequestBuilderParams} from "@/models/HTTPRequestBuilder" +import {getJsonSchemaListResult} from "@/components/JsonFormWrapper/JsonFormWrapper" +import {number} from "echarts" const {ipcRenderer} = window.require("electron") export interface CodeScanStreamInfo { @@ -250,9 +270,6 @@ export const YakRunnerCodeScan: React.FC = (props) => { // 隐藏插件列表 const [hidden, setHidden] = useState(false) - const [loading, setLoading] = useState(false) - // 如若没有已编译项目则需跳转至审计编译 - const [isShowAuditCode, setShowAuditCode] = useState(false) const pluginGroupRef = useRef(null) const [inViewport = true] = useInViewport(pluginGroupRef) @@ -279,23 +296,6 @@ export const YakRunnerCodeScan: React.FC = (props) => { return groups }, [pageInfo.selectGroupListByKeyWord]) - const getAduitList = useMemoizedFn(async () => { - try { - setLoading(true) - const {res} = await grpcFetchAuditTree("/") - if (res.Resources.length === 0) { - setShowAuditCode(true) - } - setLoading(false) - } catch (error) { - setLoading(false) - } - }) - - useEffect(() => { - getAduitList() - }, []) - const waterMarkStr = useMemo(() => { if (isCommunityEdition()) { return "Yakit技术浏览版仅供技术交流使用" @@ -305,61 +305,39 @@ export const YakRunnerCodeScan: React.FC = (props) => { return ( - - {isShowAuditCode ? ( -
- - } - onClick={() => { - addToTab(YakitRoute.YakRunner_Audit_Code) - }} - > - 代码审计 - -
- } - /> -
- ) : ( -
-
- {progressList.length === 1 && ( - + {progressShow && ( + )} {/* = React.memo > 任务列表 */} - { - e.stopPropagation() - setShowCompileModal(true) - }} - disabled={isExecuting} - style={{padding: 0}} - > - 添加项目 - {isExecuting ? !isExpand && ( <> - {/* {executeStatus === "paused" && !pauseLoading && ( + {executeType === "new" ? ( + + 停止 + + ) : ( + <> + {/* {executeStatus === "paused" && !pauseLoading && ( 继续 @@ -659,19 +637,25 @@ const CodeScanExecuteContent: React.FC = React.memo 暂停 )} */} - - 停止 - + + 停止 + + + )}
) : !isExpand && ( <> - 执行 + {executeType === "new" ? ( + 编译 + ) : ( + 执行 + )}
)} @@ -690,42 +674,60 @@ const CodeScanExecuteContent: React.FC = React.memo ref={codeScanExecuteContentRef} isExpand={isExpand} setIsExpand={setIsExpand} - setProgressList={setProgressList} + setProgressShow={setProgressShow} executeStatus={executeStatus} setExecuteStatus={onSetExecuteStatus} selectGroupList={selectGroupList} setHidden={setHidden} auditCodeList={auditCodeList} + getAduitList={getAduitList} + executeType={executeType} + setExecuteType={setExecuteType} + isAuditExecuting={isAuditExecuting} + setAuditsExecuting={setAuditsExecuting} pageInfo={pageInfo} />
- loading...}> - {visibleRaskList && ( - <> - {/* */} - - )} - - - {isShowCompileModal && ( - - )} ) }) export const CodeScanMainExecuteContent: React.FC = React.memo( forwardRef((props, ref) => { - const {isExpand, setIsExpand, setHidden, selectGroupList, setProgressList, auditCodeList, pageInfo} = props + const { + isExpand, + setIsExpand, + setHidden, + selectGroupList, + setProgressShow, + auditCodeList, + getAduitList, + executeType, + setExecuteType, + pageInfo + } = props const [form] = Form.useForm() + const [plugin, setPlugin] = useState() + /** 子组件方法传递给父组件 */ + const codeScanAuditExecuteRef = useRef(null) + // 获取参数 + const handleFetchParams = useDebounceFn( + useMemoizedFn(async () => { + const newPlugin = await grpcFetchLocalPluginDetail({Name: "SSA 项目编译"}, true) + setPlugin(newPlugin) + }), + {wait: 300} + ).run + + useEffect(() => { + handleFetchParams() + }, []) + useEffect(() => { if (pageInfo.projectName) { + setExecuteType("old") form.setFieldsValue({ project: pageInfo.projectName }) @@ -749,6 +751,12 @@ export const CodeScanMainExecuteContent: React.FC { + codeScanAuditExecuteRef.current?.onStartAuditExecute() + }, + onStopAuditExecute: () => { + codeScanAuditExecuteRef.current?.onCancelAudit() } }), [form] @@ -766,12 +774,18 @@ export const CodeScanMainExecuteContent: React.FC(props, { defaultValue: "default", valuePropName: "executeStatus", trigger: "setExecuteStatus" }) + const [isAuditExecuting, setAuditsExecuting] = useControllableValue(props, { + defaultValue: false, + valuePropName: "isAuditExecuting", + trigger: "setAuditsExecuting" + }) + const [token, setToken] = useState(randomString(20)) const isExecuting = useCreation(() => { @@ -794,17 +808,28 @@ export const CodeScanMainExecuteContent: React.FC { - messages.current.unshift({ ...log, content: { ...log.content, id: uuidv4() } }) + messages.current.unshift({...log, content: {...log.content, id: uuidv4()}}) // 只缓存 100 条结果(日志类型 + 数据类型) if (messages.current.length > 100) { messages.current.pop() } }) + /** 新项目日志队列 */ + const pushNewLogs = useMemoizedFn((log: StreamResult.Message[]) => { + messages.current = log + }) + // card let cardKVPair = useRef>( new Map() ) + const resetStreamInfo = useMemoizedFn(()=>{ + messages.current = [] + cardKVPair.current = new Map() + setRuntimeId("") + }) + useEffect(() => { let id = setInterval(() => { // logs @@ -814,7 +839,7 @@ export const CodeScanMainExecuteContent: React.FC i.data !== "null") setStreamInfo({ cardState: convertCardInfo(cardKVPair.current), - logState: logs, + logState: logs }) }, 200) return () => clearInterval(id) @@ -862,10 +887,13 @@ export const CodeScanMainExecuteContent: React.FC timestamp) { return @@ -889,13 +917,12 @@ export const CodeScanMainExecuteContent: React.FC { + const onStartExecute = useMemoizedFn(async (value, isSetForm?: boolean) => { const {project} = value + if (!project) { + warn("请输入项目名称") + return + } + // 设置表单 + if (isSetForm) { + getAduitList() + form.setFieldsValue({project}) + } const params: SyntaxFlowScanRequest = { ControlMode: "start", ProgramName: [project], @@ -941,15 +977,21 @@ export const CodeScanMainExecuteContent: React.FC { setIsExpand(false) setExecuteStatus("process") + resetStreamInfo() if (setHidden) setHidden(true) }) }) /**取消执行 */ const onStopExecute = useMemoizedFn(() => { - apiCancelSyntaxFlowScan(token).then(() => { - setExecuteStatus("finished") - }) + if (isAuditExecuting) { + codeScanAuditExecuteRef.current?.onCancelAudit() + } else { + apiCancelSyntaxFlowScan(token).then(() => { + setIsExpand(true) + setExecuteStatus("finished") + }) + } }) /**暂停 */ @@ -967,6 +1009,17 @@ export const CodeScanMainExecuteContent: React.FC { + const tabsState = [ + {tabName: "漏洞与风险", type: "risk"}, + {tabName: "日志", type: "log"}, + {tabName: "Console", type: "console"} + ] + if (runtimeId) { + return [{tabName: "审计结果", type: "result"}, ...tabsState] + } + return tabsState + }, [runtimeId]) return ( <>
-
- + + + { + setExecuteType(e.target.value) + }} + buttonStyle='solid' + options={[ + { + value: "new", + label: "新项目" + }, + { + value: "old", + label: "已编译项目" + } + ]} + /> + + + {executeType === "new" ? ( + + ) : ( + onStartExecute(value)} + labelCol={{span: 6}} + wrapperCol={{span: 12}} //这样设置是为了让输入框居中 + validateMessages={{ + /* eslint-disable no-template-curly-in-string */ + required: "${label} 是必填字段" + }} + labelWrap={true} + className={styles["code-scan-form"]} > - - - -
- {isExecuting ? ( - <> - {/* {executeStatus === "paused" && !pauseLoading && ( + + + + + +
+ {isExecuting ? ( + <> + {/* {executeStatus === "paused" && !pauseLoading && ( 继续 )} */} - {/* {(executeStatus === "process" || pauseLoading) && ( + {/* {(executeStatus === "process" || pauseLoading) && ( 暂停 )} */} - - 停止 - - - ) : ( - <> - - 开始执行 - - - )} -
-
- + + 停止 + + + ) : ( + <> + + 开始执行 + + + )} +
+
+ + )}
+ {isShowResult && ( - + )} ) }) ) +const CodeScanAuditExecuteForm: React.FC = React.memo( + forwardRef((props, ref) => { + const { + selectGroupList, + plugin, + onStartExecute, + setProgressShow, + pushNewLogs, + isAuditExecuting, + setAuditsExecuting, + setExecuteType, + setIsExpand, + setExecuteStatus, + resetStreamInfo + } = props + const [form] = Form.useForm() + const jsonSchemaListRef = useRef<{ + [key: string]: any + }>({}) + const [extraParamsVisible, setExtraParamsVisible] = useState(false) + const openExtraPropsDrawer = useMemoizedFn(() => { + setExtraParamsVisible(true) + }) + + // 必要参数 + const requiredParams = useMemo(() => { + return ( + plugin?.Params.filter((item) => !!item.Required).map((item) => { + return item + }) || [] + ) + }, [plugin?.Params]) + const [extraParamsValue, setExtraParamsValue] = useState() + /** 选填参数 */ + const groupParams = useMemo(() => { + const arr = plugin?.Params.filter((item) => !item.Required) || [] + return ParamsToGroupByGroupName(arr) + }, [plugin?.Params]) + + const tokenRef = useRef(randomString(40)) + const [runtimeId, setRuntimeId] = useState("") + const [streamInfo, debugPluginStreamEvent] = useHoldGRPCStream({ + taskName: "debug-plugin", + apiKey: "DebugPlugin", + token: tokenRef.current, + onEnd: () => { + debugPluginStreamEvent.stop() + setTimeout(() => { + setAuditsExecuting(false) + setProgressShow(undefined) + }, 300) + }, + onError: () => { + setExecuteStatus("error") + }, + setRuntimeId: (rId) => { + yakitNotify("info", `调试任务启动成功,运行时 ID: ${rId}`) + setRuntimeId(rId) + } + }) + const runnerProject = useRef() + + // 执行审计 + const onStartAudit = useMemoizedFn((path: string, requestParams: DebugPluginRequest) => { + debugPluginStreamEvent.reset() + apiDebugPlugin({params: requestParams, token: tokenRef.current}).then(() => { + resetStreamInfo() + setIsExpand(false) + setAuditsExecuting(true) + setExecuteStatus("process") + debugPluginStreamEvent.start() + }) + }) + + const onCancelAudit = () => { + apiCancelDebugPlugin(tokenRef.current).then(() => { + setIsExpand(true) + setExecuteStatus("finished") + }) + } + + useImperativeHandle( + ref, + () => ({ + onCancelAudit, + onStartAuditExecute: () => { + form.validateFields() + .then(onStartAuditFun) + .catch((e) => { + setIsExpand(true) + }) + } + }), + [] + ) + + useUpdateEffect(() => { + const progress = Math.floor((streamInfo.progressState.map((item) => item.progress)[0] || 0) * 100) / 100 + // 当任务结束时 跳转打开编译列表 + if (progress === 1) { + setTimeout(() => { + setExecuteType("old") + runnerProject.current && onStartExecute({project: runnerProject.current},true) + }, 300) + } + + let newLog = streamInfo.logState.slice(0, 100).map((item) => ({ + type: "log", + content: item + })) + pushNewLogs(newLog) + + setProgressShow({ + type: "new", + progress: progress + }) + }, [streamInfo]) + + const onStartAuditFun = useMemoizedFn(async (value) => { + if (!plugin) { + failed("插件获取失败") + return + } + const requestParams: DebugPluginRequest = { + Code: plugin.Content, + PluginType: plugin.Type, + Input: value["Input"] || "", + HTTPRequestTemplate: {} as HTTPRequestBuilderParams, + ExecParams: [], + PluginName: "" + } + + const request: Record = {} + for (let el of plugin?.Params || []) request[el.Field] = value[el.Field] || undefined + requestParams.ExecParams = getYakExecutorParam({...value, ...extraParamsValue}) + + const result = getJsonSchemaListResult(jsonSchemaListRef.current) + if (result.jsonSchemaError.length > 0) { + failed(`jsonSchema校验失败`) + return + } + result.jsonSchemaSuccess.forEach((item) => { + requestParams.ExecParams.push({ + Key: item.key, + Value: JSON.stringify(item.value) + }) + }) + runnerProject.current = value["programName"] + onStartAudit(value["programName"], requestParams) + }) + return ( +
+
+
+ +
+ +
+ {isAuditExecuting ? ( + + 停止 + + ) : ( + + 开始编译 + + )} + {groupParams.length > 0 && ( + + 额外参数 + + )} +
+
+
+ {/* 额外参数中没有JsonSchema */} + loading...
}> + + +
+ ) + }) +) + +const CodeScanExecuteExtraParamsDrawer: React.FC = React.memo((props) => { + const {groupParams, visible, setVisible, extraParamsValue, setExtraParamsValue} = props + const [form] = Form.useForm() + + useEffect(() => { + if (visible) { + form.setFieldsValue({...extraParamsValue}) + } + }, [visible, extraParamsValue]) + + const onClose = useMemoizedFn(() => { + onSaveSetting() + }) + const onSaveSetting = useMemoizedFn(() => { + form.validateFields().then((formValue) => { + setExtraParamsValue(formValue) + setVisible(false) + }) + }) + return ( + +
+ + +
+ ) +}) + /**@name 代码扫描中规则列表的item */ export const FlowRuleDetailsListItem: (props: FlowRuleDetailsListItemProps) => any = React.memo((props) => { const {data} = props diff --git a/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScanType.d.ts b/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScanType.d.ts index 9a64c40bf2..ff96392c97 100644 --- a/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScanType.d.ts +++ b/app/renderer/src/main/src/pages/yakRunnerCodeScan/YakRunnerCodeScanType.d.ts @@ -1,7 +1,6 @@ -import {GroupCount} from "@/pages/invoker/schema" import {Paging} from "@/utils/yakQueryHTTPFlow" import {DbOperateMessage} from "@/pages/layout/mainOperatorContent/utils" -import {ExecResult} from "@/pages/invoker/schema" +import {ExecResult, YakScript, GroupCount} from "@/pages/invoker/schema" import {StreamResult} from "@/hook/useHoldGRPCStream/useHoldGRPCStreamType" import {CodeScanPageInfoProps} from "@/store/pageInfo" @@ -29,6 +28,7 @@ export interface CodeScanExecuteContentProps { onClearAll: () => void selectGroupList: string[] pageInfo: CodeScanPageInfoProps + pageId: string } export interface CodeScanByGroupProps { @@ -42,7 +42,9 @@ export interface CodeScanExecuteContentRefProps { onStartExecute: () => void onPause: () => void onContinue: () => void - onSetProject: (v:string) => void + onSetProject: (v: string) => void + onStartAuditExecute: () => void + onStopAuditExecute: () => void } export interface CodeScaMainExecuteContentProps { @@ -54,10 +56,15 @@ export interface CodeScaMainExecuteContentProps { setExecuteStatus: (value: SyntaxFlowScanExecuteState) => void selectGroupList: string[] /**进度条信息 */ - setProgressList: (s: StreamResult.Progress[]) => void + setProgressShow: (s?: {type: "new" | "old"; progress: number; name?: string}) => void // 项目名称列表 - auditCodeList:{label: string; value: string}[] + auditCodeList: {label: string; value: string}[] + getAduitList: () => void pageInfo: CodeScanPageInfoProps + executeType: "new" | "old" + isAuditExecuting: boolean + setAuditsExecuting: (v: boolean) => void + setExecuteType: (v: "new" | "old") => void } export interface FlowRuleDetailsListItemProps { @@ -182,3 +189,32 @@ export interface QuerySyntaxFlowResultResponse { Results: SyntaxFlowResult[] Total: number } + +export interface CodeScanExecuteExtraParamsDrawerProps { + groupParams: YakExtraParamProps[] + visible: boolean + setVisible: (v: boolean) => void + extraParamsValue: any + setExtraParamsValue: (v: any) => void +} + +export interface CodeScanAuditExecuteRefProps { + onCancelAudit: () => void + onStartAuditExecute: () => void +} + +export interface CodeScanAuditExecuteFormProps { + ref?: React.ForwardedRef + selectGroupList: string[] + plugin?: YakScript + onStartExecute: (v: {project: string},is?: boolean) => void + /**进度条信息 */ + setProgressShow: (s?: {type: "new" | "old"; progress: number; name?: string}) => void + pushNewLogs: (log: StreamResult.Message[]) => void + isAuditExecuting: boolean + setAuditsExecuting: (v: boolean) => void + setExecuteType: (type: "new" | "old") => void + setIsExpand: (v: boolean) => void + setExecuteStatus: (value: SyntaxFlowScanExecuteState) => void + resetStreamInfo: () => void +} diff --git a/app/renderer/src/main/src/routes/newRoute.tsx b/app/renderer/src/main/src/routes/newRoute.tsx index 857a982cd2..4daed849ae 100644 --- a/app/renderer/src/main/src/routes/newRoute.tsx +++ b/app/renderer/src/main/src/routes/newRoute.tsx @@ -42,6 +42,7 @@ import { PrivateOutlinePluginStoreIcon, PrivateOutlinePocIcon, PrivateOutlinePortsIcon, + PrivateOutlineProjectManagerIcon, PrivateOutlineReportIcon, PrivateOutlineReverseServerIcon, PrivateOutlineRiskIcon, @@ -71,6 +72,7 @@ import { PrivateSolidPluginStoreIcon, PrivateSolidPocIcon, PrivateSolidPortsIcon, + PrivateSolidProjectManagerIcon, PrivateSolidReportIcon, PrivateSolidReverseServerIcon, PrivateSolidRiskIcon, @@ -137,6 +139,7 @@ import {YakRunnerCodeScan} from "@/pages/yakRunnerCodeScan/YakRunnerCodeScan" import {YakRunnerAuditCode} from "@/pages/yakRunnerAuditCode/YakRunnerAuditCode" import {AddYakitPlugin} from "@/pages/pluginEditor/addYakitPlugin/AddYakitPlugin" import {WebsocketFuzzer} from "@/pages/websocket/WebsocketFuzzer" +import { YakRunnerProjectManager } from "@/pages/YakRunnerProjectManager/YakRunnerProjectManager" const HTTPHacker = React.lazy(() => import("../pages/hacker/httpHacker")) const Home = React.lazy(() => import("@/pages/home/Home")) @@ -221,7 +224,8 @@ export const YakitRouteToPageInfo: Record ReactNode = (props) => { return case YakitRoute.YakRunner_Audit_Code: return + case YakitRoute.YakRunner_Project_Manager: + return default: return
} @@ -764,6 +772,10 @@ export const PublicRouteMenu: PublicRouteMenuProps[] = [ page: undefined, label: "代码审计", children: [ + { + page: YakitRoute.YakRunner_Project_Manager, + ...YakitRouteToPageInfo[YakitRoute.YakRunner_Project_Manager] + }, { page: YakitRoute.YakRunner_Audit_Code, ...YakitRouteToPageInfo[YakitRoute.YakRunner_Audit_Code] @@ -977,6 +989,12 @@ export const PrivateAllMenus: Record = { hoverIcon: , ...YakitRouteToPageInfo[YakitRoute.YakRunner_Audit_Code] }, + [YakitRoute.YakRunner_Project_Manager]: { + page: YakitRoute.YakRunner_Project_Manager, + icon: , + hoverIcon: , + ...YakitRouteToPageInfo[YakitRoute.YakRunner_Project_Manager] + }, [YakitRoute.YakRunner_Code_Scan]: { page: YakitRoute.YakRunner_Code_Scan, icon: , @@ -1096,7 +1114,7 @@ export const PrivateExpertRouteMenu: PrivateRouteMenuProps[] = [ { page: undefined, label: "代码审计", - children: [PrivateAllMenus[YakitRoute.YakRunner_Audit_Code], PrivateAllMenus[YakitRoute.YakRunner_Code_Scan]] + children: [PrivateAllMenus[YakitRoute.YakRunner_Project_Manager],PrivateAllMenus[YakitRoute.YakRunner_Audit_Code], PrivateAllMenus[YakitRoute.YakRunner_Code_Scan]] }, { page: undefined, diff --git a/app/renderer/src/main/src/routes/privateIcon.tsx b/app/renderer/src/main/src/routes/privateIcon.tsx index 3f22e1fa37..56c7a9bf2e 100644 --- a/app/renderer/src/main/src/routes/privateIcon.tsx +++ b/app/renderer/src/main/src/routes/privateIcon.tsx @@ -2724,3 +2724,76 @@ const PrivateSolidCodeScan = () => ( export const PrivateSolidCodeScanIcon = (props: Partial) => { return } + +const PrivateOutlineProjectManager = () => ( + + + + + + + + + + +) + +/** + * @name 项目管理 + */ +export const PrivateOutlineProjectManagerIcon = (props: Partial) => { + return +} + +const PrivateSolidProjectManager = () => ( + + + + + + + + + + +) + +export const PrivateSolidProjectManagerIcon = (props: Partial) => { + return +} diff --git a/app/renderer/src/main/src/routes/publicIcon.tsx b/app/renderer/src/main/src/routes/publicIcon.tsx index f1cdad5f2d..344879fc71 100644 --- a/app/renderer/src/main/src/routes/publicIcon.tsx +++ b/app/renderer/src/main/src/routes/publicIcon.tsx @@ -2682,3 +2682,30 @@ const PublicCodeScan = () => ( export const PublicCodeScanIcon = (props: Partial) => { return } + +const PublicProjectManager = () => ( + + + + + + + + + + +) + +export const PublicProjectManagerIcon = (props: Partial) => { + return +} diff --git a/app/renderer/src/main/src/store/pageInfo.ts b/app/renderer/src/main/src/store/pageInfo.ts index fabfeda48f..c1787d0345 100644 --- a/app/renderer/src/main/src/store/pageInfo.ts +++ b/app/renderer/src/main/src/store/pageInfo.ts @@ -178,7 +178,7 @@ export interface AuditCodePageInfoProps { Value?: string // 正常操作查询 Location: string - Query: {Key: string; Value: number}[] + Query?: {Key: string; Value: number}[] } export interface CodeScanPageInfoProps { diff --git a/app/renderer/src/main/src/utils/eventBus/events/yakRunnerAudit.ts b/app/renderer/src/main/src/utils/eventBus/events/yakRunnerAudit.ts index 4bc9adf7c1..53a6d69862 100644 --- a/app/renderer/src/main/src/utils/eventBus/events/yakRunnerAudit.ts +++ b/app/renderer/src/main/src/utils/eventBus/events/yakRunnerAudit.ts @@ -20,8 +20,8 @@ export type YakRunnerAuditEventProps = { onExecuteAuditModal?: string // 打开编译右侧详情 onCodeAuditOpenRightDetail: string - // 打开编译右侧BUG详情 - onCodeAuditOpenRightBugDetail: string + // 打开编译BUG详情 + onCodeAuditOpenBugDetail: string // 打开审计树 onCodeAuditOpenAuditTree: string // 刷新树 @@ -32,4 +32,8 @@ export type YakRunnerAuditEventProps = { onAuditCodePageInfo: string // 重置整个代码审计页面 onInitAuditCodePage?: string + // 执行审计 + onAuditRuleSubmit: string + // 停止审计 + onStopAuditRule?: string } \ No newline at end of file diff --git a/app/renderer/src/main/yarn.lock b/app/renderer/src/main/yarn.lock index 06b06a4e1f..1a121f8cf1 100644 --- a/app/renderer/src/main/yarn.lock +++ b/app/renderer/src/main/yarn.lock @@ -3024,6 +3024,48 @@ resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4" integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== +"@rjsf/antd@^5.22.4": + version "5.22.4" + resolved "https://registry.npmjs.org/@rjsf/antd/-/antd-5.22.4.tgz#869b5495c175ca57b6073b919bec831e2fab2dc8" + integrity sha512-+8y8Z0WdKQBNFI2dk/St6rS7kLIGn45YcJTd54UXSUcGwPTDVGW4OU+nhVn+NZns8JnZax3qGi2kbOiva8UiMg== + dependencies: + classnames "^2.5.1" + lodash "^4.17.21" + lodash-es "^4.17.21" + rc-picker "2.7.6" + +"@rjsf/core@^5.22.4": + version "5.22.4" + resolved "https://registry.npmjs.org/@rjsf/core/-/core-5.22.4.tgz#70c6f6aa2916575d06e242fceb6728a94c858942" + integrity sha512-0QjAVPXDi/19jR/E44ULDzOkvC4Px5zcZhpGtBFNWNWWmb9UgyjPuvJYga2obzHU46P+5maLvUQEZVAeFwDuqQ== + dependencies: + lodash "^4.17.21" + lodash-es "^4.17.21" + markdown-to-jsx "^7.4.1" + nanoid "^3.3.7" + prop-types "^15.8.1" + +"@rjsf/utils@^5.22.4": + version "5.22.4" + resolved "https://registry.npmjs.org/@rjsf/utils/-/utils-5.22.4.tgz#236f7f602d4b5ab1b3f96d87463e61219b90a143" + integrity sha512-yQTdz5ryiYy258xCVthVPQ3DeaMzrRNrFcO8xvGHorp0/bLUxdTZ0iidXop49m3y8SaxxTZd398ZKWg2cqxiIA== + dependencies: + json-schema-merge-allof "^0.8.1" + jsonpointer "^5.0.1" + lodash "^4.17.21" + lodash-es "^4.17.21" + react-is "^18.2.0" + +"@rjsf/validator-ajv8@^5.22.4": + version "5.22.4" + resolved "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.22.4.tgz#e815c5bebbc068e206f8c468d56e33076616da12" + integrity sha512-HIMmJpSY9iyN325W/3/l/2Pbt5BAPd9pf0nO+KZuW5dxE0WUThwjIsa104gFJUgig5M3RuKbelX565Qmvfa87A== + dependencies: + ajv "^8.12.0" + ajv-formats "^2.1.1" + lodash "^4.17.21" + lodash-es "^4.17.21" + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -4259,6 +4301,16 @@ ajv@^8.0.0, ajv@^8.6.0, ajv@^8.9.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@^8.12.0: + version "8.17.1" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + ali-react-table@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/ali-react-table/-/ali-react-table-2.6.1.tgz#ad6d07269943125bc71c98b81aa011413e70020c" @@ -5442,6 +5494,11 @@ classnames@*, classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== +classnames@^2.5.1: + version "2.5.1" + resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + clean-css@^5.2.2: version "5.3.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" @@ -5486,6 +5543,11 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" +codemirror@^5.65.2: + version "5.65.18" + resolved "https://registry.npmjs.org/codemirror/-/codemirror-5.65.18.tgz#d7146e4271135a9b4adcd023a270185457c9c428" + integrity sha512-Gaz4gHnkbHMGgahNt3CA5HBk5lLQBqmD/pBgeB4kQU6OedZmqMBjlRF0LSrp2tJ4wlLNPm2FfaUd1pDy0mdlpA== + codepage@~1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab" @@ -5628,6 +5690,25 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" +compute-gcd@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz#34d639f3825625e1357ce81f0e456a6249d8c77f" + integrity sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg== + dependencies: + validate.io-array "^1.0.3" + validate.io-function "^1.0.2" + validate.io-integer-array "^1.0.0" + +compute-lcm@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz#9107c66b9dca28cefb22b4ab4545caac4034af23" + integrity sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ== + dependencies: + compute-gcd "^1.2.1" + validate.io-array "^1.0.3" + validate.io-function "^1.0.2" + validate.io-integer-array "^1.0.0" + compute-scroll-into-view@^1.0.17: version "1.0.17" resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" @@ -7667,6 +7748,11 @@ fast-plist@^0.1.3: resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.3.tgz#328cd9335e93a2479ac90814a1302437574ea925" integrity sha512-d9cEfo/WcOezgPLAC/8t8wGb6YOD6JTCPMw2QcG2nAdFmyY+9rTUizCTaGjIZAloWENTEUMAPpkUAIJJJ0i96A== +fast-uri@^3.0.1: + version "3.0.3" + resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" + integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== + fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -9926,6 +10012,22 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-compare@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz#dd601508335a90c7f4cfadb6b2e397225c908e56" + integrity sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ== + dependencies: + lodash "^4.17.4" + +json-schema-merge-allof@^0.8.1: + version "0.8.1" + resolved "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz#ed2828cdd958616ff74f932830a26291789eaaf2" + integrity sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w== + dependencies: + compute-lcm "^1.1.2" + json-schema-compare "^0.2.2" + lodash "^4.17.20" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -10001,7 +10103,7 @@ jsonify@^0.0.1: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== -jsonpointer@^5.0.0: +jsonpointer@^5.0.0, jsonpointer@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== @@ -10506,6 +10608,11 @@ markdown-table@^3.0.0: resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c" integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA== +markdown-to-jsx@^7.4.1: + version "7.7.0" + resolved "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.0.tgz#98424780af4b9cccd55873c5bc7e43c0b00ff579" + integrity sha512-130nIMbJY+woOQJ11xTqEtYko60t6EpNkZuqjKMferL3udtob3nRfzXOdsiA26NPemiR7w/hR8M3/B9yiYPGZg== + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -13006,6 +13113,20 @@ rc-pagination@~3.2.0: "@babel/runtime" "^7.10.1" classnames "^2.2.1" +rc-picker@2.7.6: + version "2.7.6" + resolved "https://registry.npmjs.org/rc-picker/-/rc-picker-2.7.6.tgz#03d855888d1878d8946bab77a3d24477fd3a0792" + integrity sha512-H9if/BUJUZBOhPfWcPeT15JUI3/ntrG9muzERrXDkSoWmDj4yzmBvumozpxYrHwjcKnjyDGAke68d+whWwvhHA== + dependencies: + "@babel/runtime" "^7.10.1" + classnames "^2.2.1" + date-fns "2.x" + dayjs "1.x" + moment "^2.24.0" + rc-trigger "^5.0.4" + rc-util "^5.37.0" + shallowequal "^1.1.0" + rc-picker@~2.6.8: version "2.6.11" resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.6.11.tgz#d4a55e46480517cd1bfea5f5acd28b1d6be232d2" @@ -13293,6 +13414,14 @@ rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.12.0, rc-util@^5.15.0, rc-util@^5.16. react-is "^16.12.0" shallowequal "^1.1.0" +rc-util@^5.37.0: + version "5.43.0" + resolved "https://registry.npmjs.org/rc-util/-/rc-util-5.43.0.tgz#bba91fbef2c3e30ea2c236893746f3e9b05ecc4c" + integrity sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw== + dependencies: + "@babel/runtime" "^7.18.3" + react-is "^18.2.0" + rc-virtual-list@^3.2.0, rc-virtual-list@^3.4.8: version "3.4.11" resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.4.11.tgz#97f5e947380d546a2ca8ad229d8e41e9b33b20c6" @@ -13328,9 +13457,9 @@ react-app-rewired@^2.1.8: semver "^5.6.0" react-codemirror2@^7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-7.2.1.tgz#38dab492fcbe5fb8ebf5630e5bb7922db8d3a10c" - integrity sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw== + version "7.3.0" + resolved "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-7.3.0.tgz#c26e9fe655458389c6218496076e4bc7a4c31166" + integrity sha512-gCgJPXDX+5iaPolkHAu1YbJ92a2yL7Je4TuyO3QEqOtI/d6mbEk08l0oIm18R4ctuT/Sl87X63xIMBnRQBXYXA== react-copy-to-clipboard@^5.0.4: version "5.1.0" @@ -13483,6 +13612,11 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-is@^18.2.0: + version "18.3.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -14879,7 +15013,16 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -15011,7 +15154,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -15025,6 +15168,13 @@ strip-ansi@^3.0.0: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -15948,6 +16098,36 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" +validate.io-array@^1.0.3: + version "1.0.6" + resolved "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz#5b5a2cafd8f8b85abb2f886ba153f2d93a27774d" + integrity sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg== + +validate.io-function@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz#343a19802ed3b1968269c780e558e93411c0bad7" + integrity sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ== + +validate.io-integer-array@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz#2cabde033293a6bcbe063feafe91eaf46b13a089" + integrity sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA== + dependencies: + validate.io-array "^1.0.3" + validate.io-integer "^1.0.4" + +validate.io-integer@^1.0.4: + version "1.0.5" + resolved "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz#168496480b95be2247ec443f2233de4f89878068" + integrity sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ== + dependencies: + validate.io-number "^1.0.3" + +validate.io-number@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz#f63ffeda248bf28a67a8d48e0e3b461a1665baf8" + integrity sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -16554,7 +16734,16 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==