diff --git a/app/protos/grpc.proto b/app/protos/grpc.proto index 86db9edb19..7a18640ae8 100644 --- a/app/protos/grpc.proto +++ b/app/protos/grpc.proto @@ -166,6 +166,8 @@ service Yak { rpc YakScriptRiskTypeList(Empty) returns (YakScriptRiskTypeListResponse); rpc SaveNewYakScript(SaveNewYakScriptRequest) returns (YakScript); rpc SaveYakScriptToOnline(SaveYakScriptToOnlineRequest) returns (stream SaveYakScriptToOnlineResponse); + rpc ExportLocalYakScript(ExportLocalYakScriptRequest) returns (ExportLocalYakScriptResponse); + rpc ImportYakScript(ImportYakScriptRequest) returns (stream ImportYakScriptResult); // HTTPFlow rpc GetHTTPFlowByHash(GetHTTPFlowByHashRequest) returns (HTTPFlow); @@ -597,7 +599,7 @@ service Yak { rpc GetSpaceEngineStatus(GetSpaceEngineStatusRequest) returns(SpaceEngineStatus); rpc FetchPortAssetFromSpaceEngine(FetchPortAssetFromSpaceEngineRequest) returns (stream ExecResult); - + rpc YakVersionAtLeast(YakVersionAtLeastRequest) returns (GeneralResponse); } @@ -1018,7 +1020,7 @@ message AuthInfo { string AuthType = 3; string Host = 4; bool Forbidden = 5; - } +} message ThirdPartyApplicationConfig { // zoomeye / hunter / shodan / fofa / github / token @@ -1128,6 +1130,7 @@ message DebugPluginRequest { string Input = 3; HTTPRequestBuilderParams HTTPRequestTemplate = 4; + repeated KVPair ExecParams = 5; } message HTTPRequestBuilderResult { @@ -1529,6 +1532,7 @@ message ProjectDescription { int64 UpdateAt = 9; string FolderName = 10; string ChildFolderName = 11; + string FileSize = 12; } message GetProjectsResponse { @@ -1853,6 +1857,7 @@ message StaticAnalyzeErrorResult { message StaticAnalyzeErrorResponse { repeated StaticAnalyzeErrorResult Result = 1; } + message SavePayloadProgress { double Progress = 1; // 进度 @@ -3223,7 +3228,6 @@ message GetYakVMBuildInMethodCompletionResponse { } - message RenameRequest { string Name = 1; string NewName = 2; @@ -3467,6 +3471,31 @@ message ToOnlineResult { string ScriptName = 1; } +message ExportLocalYakScriptRequest { + string OutputDir = 1; + string OutputPluginDir = 2; + repeated int64 YakScriptIds = 3; + string Keywords = 4; + string Type = 5; + string UserName = 6; + string Tags = 7; +} + +message ExportLocalYakScriptResponse { + string OutputDir = 1; +} + +message ImportYakScriptRequest { + repeated string Dirs = 1; +} + +message ImportYakScriptResult { + // 进度 + double Progress = 1; + string Message = 2; + string MessageType = 3; +} + message GetYakScriptTagsAndTypeResponse { repeated TagsAndType Type = 1; repeated TagsAndType Tag = 2; @@ -3960,9 +3989,9 @@ message QueryHTTPFlowRequest { } message ExportHTTPFlowsRequest { - QueryHTTPFlowRequest ExportWhere = 1; - repeated int64 Ids = 2; - repeated string FieldName = 3; + QueryHTTPFlowRequest ExportWhere = 1; + repeated int64 Ids = 2; + repeated string FieldName = 3; } message DeleteHTTPFlowRequest { diff --git a/app/renderer/src/main/src/NewApp.tsx b/app/renderer/src/main/src/NewApp.tsx index 157dbbb078..a47f8f9402 100644 --- a/app/renderer/src/main/src/NewApp.tsx +++ b/app/renderer/src/main/src/NewApp.tsx @@ -1,6 +1,6 @@ import React, {useRef, useEffect, useState, Suspense, lazy} from "react" // by types -import {failed,warn} from "./utils/notification" +import {failed, warn} from "./utils/notification" import {useHotkeys} from "react-hotkeys-hook" import {getCompletions} from "./utils/monacoSpec/yakCompletionSchema" import {showModal} from "./utils/showModal" @@ -16,8 +16,10 @@ import {isCommunityEdition} from "@/utils/envfile" import {LocalGV, RemoteGV} from "./yakitGV" import {YakitModal} from "./components/yakitUI/YakitModal/YakitModal" import styles from "./app.module.scss" -import { coordinate } from "./pages/globalVariable" -import { remoteOperation } from "./pages/dynamicControl/DynamicControl" +import {coordinate} from "./pages/globalVariable" +import {remoteOperation} from "./pages/dynamicControl/DynamicControl" +import {useTemporaryProjectStore} from "./store/temporaryProject" +import {ProjectDescription} from "./pages/softwareSettings/ProjectManage" /** 快捷键目录 */ const InterceptKeyword = [ @@ -56,7 +58,7 @@ const {ipcRenderer} = window.require("electron") interface OnlineProfileProps { BaseUrl: string Password?: string - IsCompany?:boolean + IsCompany?: boolean } function NewApp() { @@ -235,15 +237,14 @@ function NewApp() { // 退出时 确保渲染进程各类事项已经处理完毕 const {dynamicStatus} = yakitDynamicStatus() useEffect(() => { - ipcRenderer.on("close-windows-renderer", async(e, res: any) => { + ipcRenderer.on("close-windows-renderer", async (e, res: any) => { // 通知应用退出 - if(dynamicStatus.isDynamicStatus){ + if (dynamicStatus.isDynamicStatus) { warn("远程控制关闭中...") - await remoteOperation(false,dynamicStatus) - ipcRenderer.invoke("app-exit") - } - else{ - ipcRenderer.invoke("app-exit") + await remoteOperation(false, dynamicStatus) + ipcRenderer.invoke("app-exit") + } else { + ipcRenderer.invoke("app-exit") } }) return () => { diff --git a/app/renderer/src/main/src/components/layout/FuncDomain.tsx b/app/renderer/src/main/src/components/layout/FuncDomain.tsx index 655257cc22..1cb5b14536 100644 --- a/app/renderer/src/main/src/components/layout/FuncDomain.tsx +++ b/app/renderer/src/main/src/components/layout/FuncDomain.tsx @@ -73,6 +73,7 @@ import classNames from "classnames" import styles from "./funcDomain.module.scss" import yakitImg from "../../assets/yakit.jpg" import {onImportPlugin} from "@/pages/fuzzer/components/ShareImport" +import { useTemporaryProjectStore } from "@/store/temporaryProject" const {ipcRenderer} = window.require("electron") const {Dragger} = Upload @@ -1010,6 +1011,8 @@ const UIOpSetting: React.FC = React.memo((props) => { const [available, setAvailable] = useState(false) // cve数据库是否可用 const [isDiffUpdate, setIsDiffUpdate] = useState(false) const {dynamicStatus} = yakitDynamicStatus() + const {temporaryProjectId, setTemporaryProjectId} = useTemporaryProjectStore() + useEffect(() => { onIsCVEDatabaseReady() }, []) @@ -1119,7 +1122,7 @@ const UIOpSetting: React.FC = React.memo((props) => { addToTab("**beta-codec") return case "invalidCache": - invalidCacheAndUserData() + invalidCacheAndUserData(temporaryProjectId, setTemporaryProjectId) return case "pcapfix": showPcapPermission() diff --git a/app/renderer/src/main/src/components/layout/MacUIOp.tsx b/app/renderer/src/main/src/components/layout/MacUIOp.tsx index c6c146e964..e89180cd2f 100644 --- a/app/renderer/src/main/src/components/layout/MacUIOp.tsx +++ b/app/renderer/src/main/src/components/layout/MacUIOp.tsx @@ -1,14 +1,22 @@ -import React, {useEffect, useState} from "react" +import React, {useEffect, useRef, useState} from "react" import {MacUIOpCloseSvgIcon, MacUIOpMaxSvgIcon, MacUIOpMinSvgIcon, MacUIOpRestoreSvgIcon} from "./icons" import {useMemoizedFn} from "ahooks" import classNames from "classnames" import styles from "./uiOperate.module.scss" import {YakitHint} from "../yakitUI/YakitHint/YakitHint" import {useRunNodeStore} from "@/store/runNode" +import {useTemporaryProjectStore} from "@/store/temporaryProject" +import {TemporaryProjectPop} from "./WinUIOp" +import emiter from "@/utils/eventBus/eventBus" +import { yakitFailed } from "@/utils/notification" +import { isEnpriTraceAgent } from "@/utils/envfile" const {ipcRenderer} = window.require("electron") -export interface MacUIOpProp {} +export interface MacUIOpProp { + currentProjectId: string // 当前项目id + pageChildrenShow: boolean +} export const MacUIOp: React.FC = React.memo((props) => { const [show, setShow] = useState(false) @@ -31,21 +39,69 @@ export const MacUIOp: React.FC = React.memo((props) => { } }, []) - /** - * 运行节点 - */ - const {runNodeList} = useRunNodeStore() + const {runNodeList, clearRunNodeList} = useRunNodeStore() const [closeRunNodeItemVerifyVisible, setCloseRunNodeItemVerifyVisible] = useState(false) + const {temporaryProjectId, temporaryProjectNoPromptFlag, setTemporaryProjectId} = useTemporaryProjectStore() + const lastTemporaryProjectIdRef = useRef("") + const [closeTemporaryProjectVisible, setCloseTemporaryProjectVisible] = useState(false) + const lastTemporaryProjectNoPromptRef = useRef(false) + const temporaryProjectPopRef = useRef(null) - const handleCloseSoft = () => { - // 如果运行节点存在 - if (Array.from(runNodeList).length) { - setCloseRunNodeItemVerifyVisible(true) - return + useEffect(() => { + lastTemporaryProjectNoPromptRef.current = temporaryProjectNoPromptFlag + }, [temporaryProjectNoPromptFlag]) + + useEffect(() => { + lastTemporaryProjectIdRef.current = temporaryProjectId + }, [temporaryProjectId]) + + const handleCloseSoft = async () => { + if (props.pageChildrenShow) { + // 如果运行节点存在 + if (Array.from(runNodeList).length) { + setCloseRunNodeItemVerifyVisible(true) + return + } + // 如果打开得是临时项目 + if ( + !isEnpriTraceAgent() && + lastTemporaryProjectIdRef.current === props.currentProjectId && + !lastTemporaryProjectNoPromptRef.current + ) { + setCloseTemporaryProjectVisible(true) + return + } else { + await handleTemporaryProject() + } + } else { + if (Array.from(runNodeList).length) { + handleKillAllRunNode() + } } operate("close") } + const handleTemporaryProject = async () => { + if (temporaryProjectId) { + await ipcRenderer.invoke("DeleteProject", {Id: +temporaryProjectId, IsDeleteLocal: true}) + setTemporaryProjectId("") + emiter.emit("onFeachGetCurrentProject") + } + } + + const handleKillAllRunNode = async () => { + let promises: (() => Promise)[] = [] + Array.from(runNodeList).forEach(([key, pid]) => { + promises.push(() => ipcRenderer.invoke("kill-run-node", {pid})) + }) + try { + await Promise.all(promises.map((promiseFunc) => promiseFunc())) + clearRunNodeList() + } catch (error) { + yakitFailed(error + "") + } + } + return (
= React.memo((props) => { visible={closeRunNodeItemVerifyVisible} title='是否确认关闭节点' content='关闭Yakit会默认关掉所有启用的节点' - onOk={() => { - operate("close") + onOk={async () => { + await handleKillAllRunNode() + setCloseRunNodeItemVerifyVisible(false) + handleCloseSoft() }} onCancel={() => { setCloseRunNodeItemVerifyVisible(false) }} /> + {/* 退出临时项目确认弹框 */} + {closeTemporaryProjectVisible && ( + { + await handleTemporaryProject() + setCloseTemporaryProjectVisible(false) + lastTemporaryProjectIdRef.current = "" + handleCloseSoft() + }} + onCancel={() => { + setCloseTemporaryProjectVisible(false) + }} + /> + )}
) diff --git a/app/renderer/src/main/src/components/layout/PerformanceDisplay.tsx b/app/renderer/src/main/src/components/layout/PerformanceDisplay.tsx index 2684e2b684..82d6e88593 100644 --- a/app/renderer/src/main/src/components/layout/PerformanceDisplay.tsx +++ b/app/renderer/src/main/src/components/layout/PerformanceDisplay.tsx @@ -1,9 +1,9 @@ import React, {useState, useEffect, useRef, useMemo} from "react" -import {failed, info, success} from "@/utils/notification" +import {failed, info, success, yakitFailed} from "@/utils/notification" import {showModal} from "@/utils/showModal" import {YaklangEngineMode} from "@/yakitGVDefine" import {LoadingOutlined} from "@ant-design/icons" -import {useInViewport, useMemoizedFn} from "ahooks" +import {useGetState, useInViewport, useMemoizedFn} from "ahooks" import {Popconfirm} from "antd" import {Sparklines, SparklinesCurve} from "react-sparklines" import {YakitButton} from "../yakitUI/YakitButton/YakitButton" @@ -13,7 +13,11 @@ import {CheckedSvgIcon, GooglePhotosLogoSvgIcon} from "./icons" import classNames from "classnames" import styles from "./performanceDisplay.module.scss" -import {YaklangEngineWatchDogCredential} from "@/components/layout/YaklangEngineWatchDog"; +import {YaklangEngineWatchDogCredential} from "@/components/layout/YaklangEngineWatchDog" +import {useRunNodeStore} from "@/store/runNode" +import emiter from "@/utils/eventBus/eventBus" +import {useTemporaryProjectStore} from "@/store/temporaryProject" +import { isEnpriTraceAgent } from "@/utils/envfile" const {ipcRenderer} = window.require("electron") @@ -75,7 +79,7 @@ export const PerformanceDisplay: React.FC = React.memo( {showLine && (
- +
)} @@ -88,7 +92,7 @@ export const PerformanceDisplay: React.FC = React.memo( export interface yakProcess { port: number pid: number - ppid?:number + ppid?: number cmd: string origin: any } @@ -111,8 +115,8 @@ const UIEngineList: React.FC = React.memo((props) => { const [psLoading, setPSLoading] = useState(false) const [process, setProcess] = useState([]) - - const [port, setPort] = useState(0) + const {runNodeList, clearRunNodeList} = useRunNodeStore() + const [port, setPort, getPort] = useGetState(0) const fetchPSList = useMemoizedFn(() => { if (psLoading) return @@ -121,15 +125,19 @@ const UIEngineList: React.FC = React.memo((props) => { ipcRenderer .invoke("ps-yak-grpc") .then((i: yakProcess[]) => { + const valuesArray = Array.from(runNodeList.values()) + // 过滤掉运行节点 setProcess( - i.map((element: yakProcess) => { - return { - port: element.port, - pid: element.pid, - cmd: element.cmd, - origin: element.origin - } - }) + i + .filter((item) => !valuesArray.includes(item.pid.toString())) + .map((element: yakProcess) => { + return { + port: element.port, + pid: element.pid, + cmd: element.cmd, + origin: element.origin + } + }) ) }) .catch((e) => { @@ -147,8 +155,7 @@ const UIEngineList: React.FC = React.memo((props) => { if (hosts.length !== 2) return if (+hosts[1]) setPort(+hosts[1]) }) - .catch(() => { - }) + .catch(() => {}) } useEffect(() => { ipcRenderer.invoke("is-dev").then((flag: boolean) => (isDev.current = flag)) @@ -166,7 +173,8 @@ const UIEngineList: React.FC = React.memo((props) => { } }, [inViewport]) - const allClose = useMemoizedFn(() => { + const allClose = useMemoizedFn(async () => { + await handleTemporaryProject() ;(process || []).forEach((i) => { ipcRenderer.invoke("kill-yak-grpc", i.pid).then((val) => { if (!val) { @@ -182,6 +190,15 @@ const UIEngineList: React.FC = React.memo((props) => { return engineMode === "admin" || engineMode === "local" }, [engineMode]) + const {temporaryProjectId, setTemporaryProjectId} = useTemporaryProjectStore() + const handleTemporaryProject = async () => { + if (temporaryProjectId) { + await ipcRenderer.invoke("DeleteProject", {Id: +temporaryProjectId, IsDeleteLocal: true}) + setTemporaryProjectId("") + emiter.emit("onFeachGetCurrentProject") + } + } + return ( = React.memo((props) => { 本地 Yak 进程管理 { - process.map(i => { + onConfirm={async () => { + await handleTemporaryProject() + process.map((i) => { ipcRenderer.invoke(`kill-yak-grpc`, i.pid) }) - ipcRenderer.invoke("RestoreEngineAndPlugin", {}).finally(() => { - info("恢复引擎成功") - ipcRenderer.invoke("relaunch") - }).catch(e => { - failed(`恢复引擎失败:${e}`) - }) + ipcRenderer + .invoke("RestoreEngineAndPlugin", {}) + .finally(() => { + info("恢复引擎成功") + ipcRenderer.invoke("relaunch") + }) + .catch((e) => { + failed(`恢复引擎失败:${e}`) + }) }} > 重置引擎版本 - {psLoading && } + {psLoading && }
{process.map((i) => { @@ -218,7 +239,7 @@ const UIEngineList: React.FC = React.memo((props) => { {`PID: ${i.pid}`} {isLocal && +i.port === port && ( - + )}
@@ -244,34 +265,38 @@ const UIEngineList: React.FC = React.memo((props) => { {isDev ? ( - 确定是否切换连接的引擎, - - } - onConfirm={() => { + title={<>确定是否切换连接的引擎,} + onConfirm={async () => { + if (+i.port !== port) { + await handleTemporaryProject() + } const switchEngine: YaklangEngineWatchDogCredential = { Mode: "local", Port: i.port, - Host: "127.0.0.1", + Host: "127.0.0.1" } ipcRenderer.invoke("switch-conn-refresh", true) ipcRenderer .invoke("connect-yaklang-engine", switchEngine) .then(() => { - - setTimeout(()=>{ + setTimeout(() => { ipcRenderer.invoke("switch-conn-refresh", false) success(`切换核心引擎成功!`) - },500) + if (!isEnpriTraceAgent() && +i.port !== port) { + emiter.emit("onSwitchEngine") + } + }, 500) }) .catch((e) => { failed(e) }) }} > - - + 切换引擎 @@ -280,27 +305,30 @@ const UIEngineList: React.FC = React.memo((props) => { title={ <> 确定关闭将会强制关闭进程, -
+
如为当前连接引擎,未关闭Yakit再次连接引擎, -
+
则需在加载页点击"其他连接模式-手动启动引擎" } - onConfirm={() => { + onConfirm={async () => { + if (+i.port === port) { + await handleTemporaryProject() + } + ipcRenderer .invoke("kill-yak-grpc", i.pid) .then((val) => { if (!val) { - if (isLocal && +i.port === port) typeCallback("break") + isLocal && +i.port === port && typeCallback("break") success("引擎进程关闭中...") } }) - .catch((e: any) => { - }) + .catch((e: any) => {}) .finally(fetchPSList) }} > - + 关闭引擎 @@ -315,9 +343,9 @@ const UIEngineList: React.FC = React.memo((props) => { title={ <> 确定关闭将会强制关闭进程, -
+
如为当前连接引擎,未关闭Yakit再次连接引擎, -
+
则需在加载页点击"其他连接模式-手动启动引擎" } @@ -333,7 +361,7 @@ const UIEngineList: React.FC = React.memo((props) => { >
- +
diff --git a/app/renderer/src/main/src/components/layout/UILayout.tsx b/app/renderer/src/main/src/components/layout/UILayout.tsx index 6f8bbd697d..6a5e1f64d3 100644 --- a/app/renderer/src/main/src/components/layout/UILayout.tsx +++ b/app/renderer/src/main/src/components/layout/UILayout.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useMemo, useRef, useState} from "react" import {useCreation, useDebounce, useGetState, useMemoizedFn} from "ahooks" -import {Form, Input, Progress, Select, Spin, Tooltip} from "antd" +import {Form, Input, Progress, Select, Spin} from "antd" import Draggable from "react-draggable" import type {DraggableEvent, DraggableData} from "react-draggable" import {MacUIOp} from "./MacUIOp" @@ -14,7 +14,7 @@ import { } from "./icons" import {PerformanceDisplay, yakProcess} from "./PerformanceDisplay" import {FuncDomain, UpdateEnpriTraceInfoProps} from "./FuncDomain" -import {WinUIOp} from "./WinUIOp" +import {TemporaryProjectPop, WinUIOp} from "./WinUIOp" import {GlobalState} from "./GlobalState" import {YakitGlobalHost} from "./YakitGlobalHost" import { @@ -24,7 +24,7 @@ import { YakitSystem, YaklangEngineMode } from "@/yakitGVDefine" -import {failed, info, success,warn} from "@/utils/notification" +import {failed, info, success, warn, yakitFailed} from "@/utils/notification" import {YakEditor} from "@/utils/editors" import {CodeGV, LocalGV, RemoteGV} from "@/yakitGV" import {EngineModeVerbose, YakitLoading} from "../basics/YakitLoading" @@ -37,7 +37,13 @@ import {YaklangEngineWatchDog, YaklangEngineWatchDogCredential} from "@/componen import {StringToUint8Array} from "@/utils/str" import {EngineLog} from "./EngineLog" import {BaseMiniConsole} from "../baseConsole/BaseConsole" -import {getReleaseEditionName, isCommunityEdition, isEnpriTrace, isEnpriTraceAgent, isEnterpriseEdition} from "@/utils/envfile" +import { + getReleaseEditionName, + isCommunityEdition, + isEnpriTrace, + isEnpriTraceAgent, + isEnterpriseEdition +} from "@/utils/envfile" import {AllKillEngineConfirm} from "./AllKillEngineConfirm" import {SoftwareSettings} from "@/pages/softwareSettings/SoftwareSettings" import {HomeSvgIcon, PolygonIcon, StopIcon} from "@/assets/newIcon" @@ -56,13 +62,17 @@ import {YakitSpin} from "../yakitUI/YakitSpin/YakitSpin" import classNames from "classnames" import styles from "./uiLayout.module.scss" import {useScreenRecorder} from "@/store/screenRecorder" -import { ResultObjProps, remoteOperation } from "@/pages/dynamicControl/DynamicControl" +import {ResultObjProps, remoteOperation} from "@/pages/dynamicControl/DynamicControl" import {useStore, yakitDynamicStatus} from "@/store" -import yakitEE from "@/assets/yakitEE.png"; -import yakitSE from "@/assets/yakitSE.png"; -import yakitCattle from "@/assets/yakitCattle.png"; -import { NetWorkApi } from "@/services/fetch" +import yakitEE from "@/assets/yakitEE.png" +import yakitSE from "@/assets/yakitSE.png" +import yakitCattle from "@/assets/yakitCattle.png" +import {NetWorkApi} from "@/services/fetch" +import {useTemporaryProjectStore} from "@/store/temporaryProject" +import {useRunNodeStore} from "@/store/runNode" +import emiter from "@/utils/eventBus/eventBus" +import { showYakitModal } from "../yakitUI/YakitModal/YakitModalConfirm" const {ipcRenderer} = window.require("electron") @@ -103,7 +113,7 @@ const UILayout: React.FC = (props) => { const [keepalive, setKeepalive] = useState(false) /** 自动远程模式 */ - const [runRemote,setRunRemote] = useState(false) + const [runRemote, setRunRemote] = useState(false) /** 内置引擎版本 */ const [buildInEngineVersion, setBuildInEngineVersion] = useState("") @@ -401,8 +411,16 @@ const UILayout: React.FC = (props) => { } }, [engineMode, localPort, adminPort]) + const handleTemporaryProject = async () => { + if (temporaryProjectId) { + await ipcRenderer.invoke("DeleteProject", {Id: +temporaryProjectId, IsDeleteLocal: true}) + setTemporaryProjectId("") + emiter.emit("onFeachGetCurrentProject") + } + } + /** yaklang引擎切换启动模式 */ - const changeEngineMode = useMemoizedFn((type: YaklangEngineMode, keepalive?: boolean) => { + const changeEngineMode = useMemoizedFn(async (type: YaklangEngineMode, keepalive?: boolean) => { info(`引擎状态切换为: ${EngineModeVerbose(type as YaklangEngineMode)}`) setYakitStatus("") @@ -419,6 +437,12 @@ const UILayout: React.FC = (props) => { } setEngineMode(undefined) + + const res = await getLocalValue(LocalGV.YaklangEngineMode) + if (!(res === "local" && type === "local")) { + handleTemporaryProject() + } + // 修改状态,重连引擎 setLocalValue(LocalGV.YaklangEngineMode, type) switch (type) { @@ -438,18 +462,18 @@ const UILayout: React.FC = (props) => { } }) - const {dynamicStatus,setDynamicStatus} = yakitDynamicStatus() + const {dynamicStatus, setDynamicStatus} = yakitDynamicStatus() const {userInfo} = useStore() - useEffect(()=>{ + useEffect(() => { // 监听退出远程控制 ipcRenderer.on("login-out-dynamic-control-callback", async (params) => { - if(dynamicStatus.isDynamicStatus){ + if (dynamicStatus.isDynamicStatus) { changeEngineMode("local") - setDynamicStatus({...dynamicStatus,isDynamicStatus:false}) - await remoteOperation(false,dynamicStatus,userInfo) + setDynamicStatus({...dynamicStatus, isDynamicStatus: false}) + await remoteOperation(false, dynamicStatus, userInfo) // 是否退出登录 - if(params?.loginOut){ + if (params?.loginOut) { ipcRenderer.invoke("ipc-sign-out") } } @@ -457,36 +481,36 @@ const UILayout: React.FC = (props) => { return () => { ipcRenderer.removeAllListeners("login-out-dynamic-control-callback") } - },[dynamicStatus.isDynamicStatus]) + }, [dynamicStatus.isDynamicStatus]) /** yaklang远程控制-自动远程模式连接 */ - const runControlRemote = useMemoizedFn((v:string,baseUrl:string)=>{ + const runControlRemote = useMemoizedFn((v: string, baseUrl: string) => { try { const resultObj: ResultObjProps = JSON.parse(v) - console.log("runControlRemote",resultObj,baseUrl) - - // 缓存远程控制参数 - setDynamicStatus({...dynamicStatus,baseUrl,...resultObj}) - ipcRenderer - .invoke("Codec", {Type: "base64-decode", Text: resultObj.pubpem, Params: [], ScriptName: ""}) - .then((res) => { - setYakitStatus("control-remote") - cacheYakitStatus.current = "control-remote" - setRunRemote(true) - setKeepalive(false) - setEngineLink(false) + console.log("runControlRemote", resultObj, baseUrl) - setCredential({ - Host: resultObj.host, - IsTLS: true, - Password: resultObj.secret, - PemBytes: StringToUint8Array(res?.Result || ""), - Port: resultObj.port, - Mode: "remote" - }) - }) - .catch((err) => { - warn(`Base64 解码失败:${err}`) - }) + // 缓存远程控制参数 + setDynamicStatus({...dynamicStatus, baseUrl, ...resultObj}) + ipcRenderer + .invoke("Codec", {Type: "base64-decode", Text: resultObj.pubpem, Params: [], ScriptName: ""}) + .then((res) => { + setYakitStatus("control-remote") + cacheYakitStatus.current = "control-remote" + setRunRemote(true) + setKeepalive(false) + setEngineLink(false) + + setCredential({ + Host: resultObj.host, + IsTLS: true, + Password: resultObj.secret, + PemBytes: StringToUint8Array(res?.Result || ""), + Port: resultObj.port, + Mode: "remote" + }) + }) + .catch((err) => { + warn(`Base64 解码失败:${err}`) + }) } catch (error) { warn(`解析失败:${error}`) } @@ -530,7 +554,7 @@ const UILayout: React.FC = (props) => { /** yaklang远程控制 - 自动连接远程模式引擎的内容 */ const connectControlRemoteEngine = useMemoizedFn(() => { setRemoteConnectLoading(true) - ipcRenderer.invoke("engine-ready-link",true) + ipcRenderer.invoke("engine-ready-link", true) setTimeout(() => { setRemoteConnectLoading(false) }, 300) @@ -583,23 +607,38 @@ const UILayout: React.FC = (props) => { visible: false }) const [projectModalLoading, setProjectModalLoading] = useState(false) - const [ProjectName,setProjectName] = useState() - const fetchCurrentProject = useMemoizedFn(() => { - ipcRenderer.invoke("GetCurrentProject").then((rsp: ProjectDescription) => { - if(rsp&&rsp.ProjectName) setProjectName(rsp.ProjectName) - setCurrentProject(rsp || undefined) - }) - }) + const [ProjectName, setProjectName] = useState() + + useEffect(() => { + const funCallBack = () => { + ipcRenderer.invoke("GetCurrentProject").then((rsp: ProjectDescription) => { + setCurrentProject(rsp || undefined) + }) + } + emiter.on("onFeachGetCurrentProject", funCallBack) + return () => { + emiter.off("onFeachGetCurrentProject", funCallBack) + } + }, []) + + const {temporaryProjectId, temporaryProjectNoPromptFlag, setTemporaryProjectId, setTemporaryProjectNoPromptFlag} = + useTemporaryProjectStore() + const [closeTemporaryProjectVisible, setCloseTemporaryProjectVisible] = useState(false) + const temporaryProjectPopRef = useRef(null) - const getAppTitleName:string = useMemo(()=>{ + const getAppTitleName: string = useMemo(() => { // 引擎未连接或便携版 显示默认title - if(!engineLink || isEnpriTraceAgent()) return getReleaseEditionName() - else { - return ProjectName? - ProjectName.length>10?`${ProjectName.slice(0,10)}...`:ProjectName - :getReleaseEditionName() + if (!engineLink || isEnpriTraceAgent()) return getReleaseEditionName() + else if (temporaryProjectId && temporaryProjectId === (currentProject?.Id ? currentProject?.Id + "" : "")) { + return "临时项目" + } else { + return ProjectName + ? ProjectName.length > 10 + ? `${ProjectName.slice(0, 10)}...` + : ProjectName + : getReleaseEditionName() } - },[ProjectName,engineLink]) + }, [ProjectName, engineLink, temporaryProjectId, currentProject]) /** funcDomain组件的回调事件 */ const typeCallback = useMemoizedFn((type: YakitSettingCallbackType) => { @@ -634,6 +673,25 @@ const UILayout: React.FC = (props) => { failed("当前项目无关键信息,无法导出!") return } + // 临时项目暂不支持导出 + if (temporaryProjectId) { + const m = showYakitModal({ + title: "提示", + content: ( +
+ 临时项目导出还在开发中,会安排尽快上线 +
+ ), + onCancel: () => { + m.destroy() + }, + onOk: () => { + m.destroy() + }, + width: 400 + }) + return + } setLinkDatabase(true) setProjectModalInfo({visible: true, isNew: false, isExport: true, project: currentProject}) return @@ -642,6 +700,25 @@ const UILayout: React.FC = (props) => { failed("当前项目无关键信息,无法导出!") return } + // 临时项目暂不支持导出 + if (temporaryProjectId) { + const m = showYakitModal({ + title: "提示", + content: ( +
+ 临时项目导出还在开发中,会安排尽快上线 +
+ ), + onCancel: () => { + m.destroy() + }, + onOk: () => { + m.destroy() + }, + width: 400 + }) + return + } setLinkDatabase(true) setProjectTransferShow({ visible: true, @@ -770,14 +847,35 @@ const UILayout: React.FC = (props) => { const [yakitMode, setYakitMode] = useState<"soft" | "store" | "">("") const changeYakitMode = useMemoizedFn((type: "soft" | "store") => { if (type === "soft" && yakitMode !== "soft") { - setLinkDatabaseHint(true) + if (temporaryProjectId && !temporaryProjectNoPromptFlag) { + setCloseTemporaryProjectVisible(true) + } else { + setLinkDatabaseHint(true) + } } }) + + const onOkEnterProjectMag = () => { + setYakitMode("soft") + setLinkDatabase(true) + setProjectName("") + } + + useEffect(() => { + emiter.on("onSwitchEngine", onOkEnterProjectMag) + return () => { + emiter.off("onSwitchEngine", onOkEnterProjectMag) + } + }, []) + /** 软件配置界面完成事件回调 */ const softwareSettingFinish = useMemoizedFn(() => { setYakitMode("") setLinkDatabase(false) - fetchCurrentProject() + ipcRenderer.invoke("GetCurrentProject").then((rsp: ProjectDescription) => { + if (rsp && rsp.ProjectName) setProjectName(rsp.ProjectName) + setCurrentProject(rsp || undefined) + }) }) /** MACOS 上双击放大窗口(不是最大化) */ @@ -807,18 +905,12 @@ const UILayout: React.FC = (props) => { if (!getEngineLink()) { isEnpriTraceAgent() ? setEngineLink(true) - : getRemoteValue(RemoteGV.LinkDatabase).then((id: number) => { - if (id) { - ipcRenderer.invoke("SetCurrentProject", {Id: +id}) - setLinkDatabase(false) - setYakitMode("") - fetchCurrentProject() - } else { - setLinkDatabase(true) - setYakitMode("soft") - } + : (async () => { + setTemporaryProjectId((await getRemoteValue(RemoteGV.TemporaryProjectId)) || "") + setLinkDatabase(true) + setYakitMode("soft") setTimeout(() => setEngineLink(true), 100) - }) + })() } if (latestYakit) setLatestYakit("") @@ -845,10 +937,10 @@ const UILayout: React.FC = (props) => { setYakitStatus("error") } // 远程控制异常退出 - if(dynamicStatus.isDynamicStatus){ - setDynamicStatus({...dynamicStatus,isDynamicStatus:false}) + if (dynamicStatus.isDynamicStatus) { + setDynamicStatus({...dynamicStatus, isDynamicStatus: false}) changeEngineMode("local") - remoteOperation(false,dynamicStatus,userInfo) + remoteOperation(false, dynamicStatus, userInfo) } } } else { @@ -857,10 +949,10 @@ const UILayout: React.FC = (props) => { setYakitStatus("error") } // 远程控制异常退出 - if(dynamicStatus.isDynamicStatus){ - setDynamicStatus({...dynamicStatus,isDynamicStatus:false}) + if (dynamicStatus.isDynamicStatus) { + setDynamicStatus({...dynamicStatus, isDynamicStatus: false}) changeEngineMode("local") - remoteOperation(false,dynamicStatus,userInfo) + remoteOperation(false, dynamicStatus, userInfo) } } }) @@ -960,7 +1052,7 @@ const UILayout: React.FC = (props) => { ipcRenderer.invoke("cancel-StartScrecorder", screenRecorderInfo.token) }} type='primary' - colors="danger" + colors='danger' className={styles["stop-screen-recorder"]} size='large' > @@ -987,6 +1079,15 @@ const UILayout: React.FC = (props) => { setRemoteValue(RemoteGV.KnowChatCS, "true") }) + // 判断是否显示页面children + const pageChildrenShow: boolean = useMemo(() => { + const flag = engineLink && !isJudgeLicense && !linkDatabase + if (!flag) { + setProjectName("") + } + return flag + }, [engineLink, isJudgeLicense, linkDatabase]) + return (
@@ -1012,12 +1113,17 @@ const UILayout: React.FC = (props) => { >
- {getAppTitleName}-{`${EngineModeVerbose(engineMode || "local",dynamicStatus)}`} + <> + {getAppTitleName}-{`${EngineModeVerbose(engineMode || "local", dynamicStatus)}`} +
- +
{engineLink && ( @@ -1120,7 +1226,9 @@ const UILayout: React.FC = (props) => { >
- {getAppTitleName}-{`${EngineModeVerbose(engineMode || "local",dynamicStatus)}`} + <> + {getAppTitleName}-{`${EngineModeVerbose(engineMode || "local", dynamicStatus)}`} +
@@ -1215,7 +1323,10 @@ const UILayout: React.FC = (props) => {
)} - +
)} @@ -1358,28 +1469,43 @@ const UILayout: React.FC = (props) => { title='是否进入项目管理' content='如果有正在进行中的任务,回到项目管理页则都会停止,确定回到项目管理页面吗?' onOk={() => { - setYakitMode("soft") - setLinkDatabase(true) + onOkEnterProjectMag() setLinkDatabaseHint(false) }} onCancel={() => setLinkDatabaseHint(false)} /> + {closeTemporaryProjectVisible && ( + { + setTemporaryProjectNoPromptFlag(temporaryProjectPopRef.current.temporaryProjectNoPrompt) + setCloseTemporaryProjectVisible(false) + onOkEnterProjectMag() + }} + onCancel={() => { + setCloseTemporaryProjectVisible(false) + }} + /> + )} + {isCommunityEdition() && engineLink && !isJudgeLicense && !linkDatabase && showChatCS && (
-
-
-
-
ChatCS
-
与安全有关的问题都可以问牛牛哦~
+
+
+
+
ChatCS
+
与安全有关的问题都可以问牛牛哦~
+
+
+ 我知道了
-
我知道了
-
+
-
+
@@ -1501,16 +1627,16 @@ const RemoteYaklangEngine: React.FC = React.memo((prop
{isCommunityEdition() && } - {isEnpriTrace()&& + {isEnpriTrace() && (
- 暂无图片 + 暂无图片
- } - {isEnpriTraceAgent()&& + )} + {isEnpriTraceAgent() && (
- 暂无图片 + 暂无图片
- } + )}
远程模式
连接历史
@@ -1827,7 +1953,7 @@ const DownloadYakit: React.FC = React.memo((props) => { useEffect(() => { if (visible) { // 社区更新 - if(isCommunityEdition()){ + if (isCommunityEdition()) { isBreakRef.current = true setDownloadProgress(undefined) ipcRenderer @@ -1835,7 +1961,7 @@ const DownloadYakit: React.FC = React.memo((props) => { .then((data: string) => { let version = data if (version.startsWith("v")) version = version.substr(1) - + ipcRenderer .invoke("download-latest-yakit", version, isEnterpriseEdition()) .then(() => { @@ -1868,57 +1994,56 @@ const DownloadYakit: React.FC = React.memo((props) => { }) } // 企业版更新 - else if(isEnpriTrace()){ + else if (isEnpriTrace()) { isBreakRef.current = true ipcRenderer.invoke("update-enpritrace-info").then((info: UpdateEnpriTraceInfoProps) => { const {version} = info NetWorkApi({ - method: "get", - url: "download/install/package", - params: {version} - }) - .then((res:{from:string,ok:boolean}) => { - const {from,ok} = res - if(ok){ - getRemoteValue(RemoteGV.HttpSetting).then((setting) => { - if (!setting) return - const value = JSON.parse(setting) - const searchStr = "/yakit-projects"; - const startIndex = from.indexOf(searchStr) + searchStr.length;; - const result = from.substring(startIndex); - const url = value.BaseUrl.replace(/:\d+$/, '') + result - ipcRenderer - .invoke("download-enpriTrace-latest-yakit", url) - .then(() => { - if (!isBreakRef.current) return - success("下载完毕") - if (!getDownloadProgress()?.size) return - setDownloadProgress({ - time: { - elapsed: downloadProgress?.time.elapsed || 0, - remaining: 0 - }, - speed: 0, - percent: 100, - // @ts-ignore - size: getDownloadProgress().size - }) - ipcRenderer.invoke("open-yakit-or-yaklang") - ipcRenderer.invoke("download-update-wait", "yakit") - }) - .catch((e: any) => { - if (!isBreakRef.current) return - failed(`下载失败: ${e}`) - }) - .finally(() => setVisible(false)) - }) - } - }) - .catch((err) => { - failed(`下载yakit安装包失败${err}`) + method: "get", + url: "download/install/package", + params: {version} }) + .then((res: {from: string; ok: boolean}) => { + const {from, ok} = res + if (ok) { + getRemoteValue(RemoteGV.HttpSetting).then((setting) => { + if (!setting) return + const value = JSON.parse(setting) + const searchStr = "/yakit-projects" + const startIndex = from.indexOf(searchStr) + searchStr.length + const result = from.substring(startIndex) + const url = value.BaseUrl.replace(/:\d+$/, "") + result + ipcRenderer + .invoke("download-enpriTrace-latest-yakit", url) + .then(() => { + if (!isBreakRef.current) return + success("下载完毕") + if (!getDownloadProgress()?.size) return + setDownloadProgress({ + time: { + elapsed: downloadProgress?.time.elapsed || 0, + remaining: 0 + }, + speed: 0, + percent: 100, + // @ts-ignore + size: getDownloadProgress().size + }) + ipcRenderer.invoke("open-yakit-or-yaklang") + ipcRenderer.invoke("download-update-wait", "yakit") + }) + .catch((e: any) => { + if (!isBreakRef.current) return + failed(`下载失败: ${e}`) + }) + .finally(() => setVisible(false)) + }) + } + }) + .catch((err) => { + failed(`下载yakit安装包失败${err}`) + }) }) - } ipcRenderer.on("download-yakit-engine-progress", (e: any, state: DownloadingState) => { @@ -1929,7 +2054,6 @@ const DownloadYakit: React.FC = React.memo((props) => { return () => { ipcRenderer.removeAllListeners("download-yakit-engine-progress") } - } else { isBreakRef.current = false } @@ -1998,7 +2122,9 @@ const DownloadYakit: React.FC = React.memo((props) => {
-
{getReleaseEditionName()} 软件下载中...
+
+ {getReleaseEditionName()} 软件下载中... +
= React.memo((props) => { const [isMax, setIsMax] = useState(false) @@ -32,21 +40,69 @@ export const WinUIOp: React.FC = React.memo((props) => { } }, []) - /** - * 运行节点 - */ - const {runNodeList} = useRunNodeStore() + const {runNodeList, clearRunNodeList} = useRunNodeStore() const [closeRunNodeItemVerifyVisible, setCloseRunNodeItemVerifyVisible] = useState(false) + const {temporaryProjectId, temporaryProjectNoPromptFlag, setTemporaryProjectId} = useTemporaryProjectStore() + const lastTemporaryProjectIdRef = useRef("") + const [closeTemporaryProjectVisible, setCloseTemporaryProjectVisible] = useState(false) + const lastTemporaryProjectNoPromptRef = useRef(false) + const temporaryProjectPopRef = useRef(null) - const handleCloseSoft = () => { - // 如果运行节点存在 - if (Array.from(runNodeList).length) { - setCloseRunNodeItemVerifyVisible(true) - return + useEffect(() => { + lastTemporaryProjectNoPromptRef.current = temporaryProjectNoPromptFlag + }, [temporaryProjectNoPromptFlag]) + + useEffect(() => { + lastTemporaryProjectIdRef.current = temporaryProjectId + }, [temporaryProjectId]) + + const handleCloseSoft = async () => { + if (props.pageChildrenShow) { + // 如果运行节点存在 + if (Array.from(runNodeList).length) { + setCloseRunNodeItemVerifyVisible(true) + return + } + // 如果打开得是临时项目 + if ( + !isEnpriTraceAgent() && + lastTemporaryProjectIdRef.current === props.currentProjectId && + !lastTemporaryProjectNoPromptRef.current + ) { + setCloseTemporaryProjectVisible(true) + return + } else { + await handleTemporaryProject() + } + } else { + if (Array.from(runNodeList).length) { + handleKillAllRunNode() + } } operate("close") } + const handleTemporaryProject = async () => { + if (temporaryProjectId) { + await ipcRenderer.invoke("DeleteProject", {Id: +temporaryProjectId, IsDeleteLocal: true}) + setTemporaryProjectId("") + emiter.emit("onFeachGetCurrentProject") + } + } + + const handleKillAllRunNode = async () => { + let promises: (() => Promise)[] = [] + Array.from(runNodeList).forEach(([key, pid]) => { + promises.push(() => ipcRenderer.invoke("kill-run-node", {pid})) + }) + try { + await Promise.all(promises.map((promiseFunc) => promiseFunc())) + clearRunNodeList() + } catch (error) { + yakitFailed(error + "") + } + } + return (
= React.memo((props) => { visible={closeRunNodeItemVerifyVisible} title='是否确认关闭节点' content='关闭Yakit会默认关掉所有启用的节点' - onOk={() => { - operate("close") + onOk={async () => { + await handleKillAllRunNode() + setCloseRunNodeItemVerifyVisible(false) + handleCloseSoft() }} onCancel={() => { setCloseRunNodeItemVerifyVisible(false) }} /> + {/* 退出临时项目确认弹框 */} + {closeTemporaryProjectVisible && ( + { + await handleTemporaryProject() + setCloseTemporaryProjectVisible(false) + lastTemporaryProjectIdRef.current = "" + handleCloseSoft() + }} + onCancel={() => { + setCloseTemporaryProjectVisible(false) + }} + /> + )}
) }) + +interface TemporaryProjectPopProp { + ref: React.Ref + onOk: () => void + onCancel: () => void +} + +export const TemporaryProjectPop: React.FC = React.forwardRef((props, ref) => { + const [temporaryProjectNoPrompt, setTemporaryProjectNoPrompt] = useState(false) + + useImperativeHandle(ref, () => ({ + temporaryProjectNoPrompt + })) + + return ( + setTemporaryProjectNoPrompt(e.target.checked)} + > + 下次不再提醒 + + } + content={ + <> +
确认退出后,临时项目所有数据都不会保存,包括流量数据、端口数据、域名数据和漏洞数据等。
+
退出前可在设置-项目管理中导出数据
+ + } + onOk={props.onOk} + onCancel={props.onCancel} + /> + ) +}) diff --git a/app/renderer/src/main/src/pages/softwareSettings/ProjectManage.module.scss b/app/renderer/src/main/src/pages/softwareSettings/ProjectManage.module.scss index 05d861eae8..ad64542579 100644 --- a/app/renderer/src/main/src/pages/softwareSettings/ProjectManage.module.scss +++ b/app/renderer/src/main/src/pages/softwareSettings/ProjectManage.module.scss @@ -138,6 +138,19 @@ } } + .new-temporary-project-wrapper { + background: rgba(53, 216, 238, 0.1); + .temporary-project-icon { + svg { + width: 32px; + height: 32px; + } + } + } + .new-temporary-project-wrapper:hover { + background: rgba(53, 216, 238, 0.2); + } + .new-project-wrapper { background: rgba(86, 201, 145, 0.1); } diff --git a/app/renderer/src/main/src/pages/softwareSettings/ProjectManage.tsx b/app/renderer/src/main/src/pages/softwareSettings/ProjectManage.tsx index 08b8e75ff2..f2ee2cf219 100644 --- a/app/renderer/src/main/src/pages/softwareSettings/ProjectManage.tsx +++ b/app/renderer/src/main/src/pages/softwareSettings/ProjectManage.tsx @@ -2,7 +2,7 @@ import React, {memo, ReactNode, useEffect, useMemo, useRef, useState} from "reac import {useDebounceEffect, useGetState, useMemoizedFn, useScroll, useVirtualList} from "ahooks" import {YakitInput} from "@/components/yakitUI/YakitInput/YakitInput" import {QueryGeneralRequest} from "../invoker/schema" -import {failed, info} from "@/utils/notification" +import {failed, info, yakitFailed} from "@/utils/notification" import { ChevronDownIcon, ChevronRightIcon, @@ -25,7 +25,8 @@ import { ProjectExportSvgIcon, ProjectFolderOpenSvgIcon, ProjectImportSvgIcon, - ProjectViewGridSvgIcon + ProjectViewGridSvgIcon, + TemporaryProjectSvgIcon } from "./icon" import ReactResizeDetector from "react-resize-detector" import {CopyComponents} from "@/components/yakitUI/YakitTag/YakitTag" @@ -37,8 +38,6 @@ import {YakitButton} from "@/components/yakitUI/YakitButton/YakitButton" import {randomString} from "@/utils/randomUtil" import {openABSFileLocated} from "@/utils/openWebsite" import {YakitSpin} from "@/components/yakitUI/YakitSpin/YakitSpin" -import {setRemoteValue} from "@/utils/kv" -import {RemoteGV} from "@/yakitGV" import {YaklangEngineMode} from "@/yakitGVDefine" import {YakitHint} from "@/components/yakitUI/YakitHint/YakitHint" import {YakitEmpty} from "@/components/yakitUI/YakitEmpty/YakitEmpty" @@ -46,6 +45,8 @@ import {showByRightContext} from "@/components/yakitUI/YakitMenu/showByRightCont import classNames from "classnames" import styles from "./ProjectManage.module.scss" +import {useTemporaryProjectStore} from "@/store/temporaryProject" +import emiter from "@/utils/eventBus/eventBus" const {ipcRenderer} = window.require("electron") @@ -55,7 +56,7 @@ export interface ProjectManageProp { onFinish: () => any } /** (新建|编辑)项目|文件夹参数 */ -interface ProjectParamsProps { +export interface ProjectParamsProps { Id?: number ProjectName: string Description?: string @@ -84,6 +85,7 @@ export interface ProjectDescription { ChildFolderId: number ChildFolderName: string Type: string + FileSize: string } export interface ProjectsResponse { Pagination: {Page: number; Limit: number} @@ -146,7 +148,8 @@ const DefaultProjectInfo: ProjectDescription = { FolderName: "", ChildFolderId: 0, ChildFolderName: "", - Type: "" + Type: "", + FileSize: "" } const ProjectManage: React.FC = memo((props) => { @@ -169,7 +172,6 @@ const ProjectManage: React.FC = memo((props) => { const [search, setSearch] = useState<{name: string; total: number}>({name: "", total: 0}) const [latestProject, setLatestProject] = useState() - const [defaultProject, setDefaultProject] = useState() const [vlistHeigth, setVListHeight] = useState(600) const containerRef = useRef(null) @@ -229,7 +231,7 @@ const ProjectManage: React.FC = memo((props) => { { key: "ProjectName", name: typeToName["all"], - width: "20%", + width: "10%", headerRender: (index) => { return ( = memo((props) => { ) } }, + {key: "FileSize", name: "大小", width: "10%"}, { key: "CreatedAt", name: timeToName["updated_at"], @@ -478,11 +481,27 @@ const ProjectManage: React.FC = memo((props) => { ) }) - useEffect(() => { - ipcRenderer.invoke("GetCurrentProject").then((rsp: ProjectDescription) => setLatestProject(rsp || undefined)) - ipcRenderer.invoke("GetDefaultProject").then((rsp: ProjectDescription) => setDefaultProject(rsp || undefined)) + const handleTemporaryProject = async () => { + if (temporaryProjectId) { + await ipcRenderer.invoke("DeleteProject", {Id: +temporaryProjectId, IsDeleteLocal: true}) + setTemporaryProjectId("") + emiter.emit("onFeachGetCurrentProject") + } + } + + const getProjectInfo = async () => { + try { + await handleTemporaryProject() + const res2: ProjectDescription = await ipcRenderer.invoke("GetCurrentProject") + setLatestProject(res2 || undefined) + update() + } catch (error) { + yakitFailed(error + "") + } + } - update() + useEffect(() => { + getProjectInfo() }, []) const update = useMemoizedFn((page?: number) => { @@ -535,63 +554,29 @@ const ProjectManage: React.FC = memo((props) => { } setLoading(true) - if (defaultProject && latestProject && delId.Id === +latestProject?.Id) { - ipcRenderer - .invoke("SetCurrentProject", { - Id: +defaultProject.Id - }) - .then((e) => { - ipcRenderer - .invoke("DeleteProject", {Id: +delId.Id, IsDeleteLocal: isDel}) - .then((e) => { - info("删除成功") - setData({ - ...getData(), - Projects: getData().Projects.filter((item) => +item.Id !== +delId.Id), - Total: getData().Total == 0 ? 0 : getData().Total - 1 - }) - ipcRenderer - .invoke("GetCurrentProject") - .then((rsp: ProjectDescription) => setLatestProject(rsp || undefined)) - }) - .catch((e) => { - failed(`删除失败: ${e}`) - }) - .finally(() => { - setDelId({Id: -1, Type: "project"}) - setDelShow(false) - setTimeout(() => setLoading(false), 300) - }) - }) - .catch(() => { - failed("删除失败,没有可以切换的默认数据库") - setDelId({Id: -1, Type: "project"}) - setDelShow(false) - setTimeout(() => setLoading(false), 300) - }) - } else { - ipcRenderer - .invoke("DeleteProject", {Id: +delId.Id, IsDeleteLocal: isDel}) - .then((e) => { - info("删除成功") - setData({ - ...getData(), - Projects: getData().Projects.filter((item) => +item.Id !== +delId.Id), - Total: getData().Total == 0 ? 0 : getData().Total - 1 - }) - ipcRenderer - .invoke("GetCurrentProject") - .then((rsp: ProjectDescription) => setLatestProject(rsp || undefined)) - }) - .catch((e) => { - failed(`删除失败: ${e}`) - }) - .finally(() => { - setDelId({Id: -1, Type: "project"}) - setDelShow(false) - setTimeout(() => setLoading(false), 300) + ipcRenderer + .invoke("DeleteProject", {Id: +delId.Id, IsDeleteLocal: isDel}) + .then((e) => { + emiter.emit("onFeachGetCurrentProject") + info("删除成功") + setData({ + ...getData(), + Projects: getData().Projects.filter((item) => +item.Id !== +delId.Id), + Total: getData().Total == 0 ? 0 : getData().Total - 1, + ProjectToTal: getData().ProjectToTal == 0 ? 0 : getData().ProjectToTal - 1 }) - } + ipcRenderer + .invoke("GetCurrentProject") + .then((rsp: ProjectDescription) => setLatestProject(rsp || undefined)) + }) + .catch((e) => { + failed(`删除失败: ${e}`) + }) + .finally(() => { + setDelId({Id: -1, Type: "project"}) + setDelShow(false) + setTimeout(() => setLoading(false), 300) + }) }) const operateFunc = useMemoizedFn((type: string, data?: ProjectDescription) => { @@ -653,7 +638,6 @@ const ProjectManage: React.FC = memo((props) => { .invoke("SetCurrentProject", {Id: data.Id}) .then((e) => { info("已切换数据库") - setRemoteValue(RemoteGV.LinkDatabase, `${data.Id}`) onFinish() }) .catch((e) => { @@ -703,6 +687,29 @@ const ProjectManage: React.FC = memo((props) => { }>({visible: false}) const [modalLoading, setModalLoading] = useState(false) + const {temporaryProjectId, setTemporaryProjectId} = useTemporaryProjectStore() + + // 创建临时项目 + const creatTemporaryProject = useMemoizedFn(async () => { + try { + const res = await ipcRenderer.invoke("NewProject", { + Type: "project", + ProjectName: "[temporary]" + }) + const newTemporaryId = res.Id + "" + setTemporaryProjectId(newTemporaryId) + // setRemoteValue(RemoteGV.TemporaryProjectId, newTemporaryId) + await ipcRenderer.invoke("SetCurrentProject", {Id: newTemporaryId}) + info("切换临时项目成功") + onFinish() + } catch (error) { + yakitFailed(error + "") + } + }) + + const [inquireIntoProjectVisible, setInquireIntoProjectVisible] = useState(false) + const [newProjectInfo, setNewProjectInfo] = useState<{Id: string; ProjectName: string}>({Id: "", ProjectName: ""}) + /** 弹窗确认事件的回调 */ const onModalSubmit = useMemoizedFn( (type: string, value: ProjectFolderInfoProps | ExportProjectProps | ImportProjectProps) => { @@ -720,11 +727,15 @@ const ProjectManage: React.FC = memo((props) => { if (projectInfo.Id) newProject.Id = +projectInfo.Id ipcRenderer .invoke("NewProject", newProject) - .then(() => { + .then((res) => { info(projectInfo.Id ? "编辑项目成功" : "创建新项目成功") setModalInfo({visible: false}) setParams({...params, Pagination: {...params.Pagination, Page: 1}}) - setTimeout(() => update(), 300) + setNewProjectInfo(res) + setTimeout(() => { + setInquireIntoProjectVisible(true) + update() + }, 300) }) .catch((e) => { failed(`${projectInfo.Id ? "编辑" : "创建新"}项目失败:${e}`) @@ -741,11 +752,15 @@ const ProjectManage: React.FC = memo((props) => { if (projectInfo.Id) newProject.Id = +projectInfo.Id ipcRenderer .invoke("NewProject", newProject) - .then(() => { + .then((res) => { info(projectInfo.Id ? "编辑项目成功" : "创建新项目成功") setModalInfo({visible: false}) setParams({...params, Pagination: {...params.Pagination, Page: 1}}) - setTimeout(() => update(), 300) + setNewProjectInfo(res) + setTimeout(() => { + setInquireIntoProjectVisible(true) + update() + }, 300) }) .catch((e) => { failed(`${projectInfo.Id ? "编辑" : "创建新"}项目失败:${e}`) @@ -985,83 +1000,96 @@ const ProjectManage: React.FC = memo((props) => {
{/* { engineMode !== "remote" && ( */} -
e.stopPropagation()}> - setHeaderShow(open) - }} - menu={{ - data: [ - { - key: "export", - label: "导出", - itemIcon: ( - - ), - children: [ - {key: "encryption", label: "加密导出"}, - {key: "plaintext", label: "明文导出"} - ] - }, - { - key: "edit", - label: "编辑", - disabled: latestProject?.ProjectName === "[default]", - itemIcon: ( - - ) - }, - { - key: "copyPath", - label: "复制路径", - itemIcon: ( - - ) - }, - {type: "divider"}, - { - key: "delete", - label: "删除", - disabled: latestProject?.ProjectName === "[default]", - itemIcon: - } - ], - className: styles["dropdown-menu-body"], - popupClassName: styles["dropdown-menu-filter-wrapper"], - onClick: ({key}) => { - setHeaderShow(false) - if (key === "delete") { - if (latestProject) { - setDelId({Id: +latestProject.Id, Type: latestProject.Type}) - setDelShow(true) - } - } else operateFunc(key, latestProject) +
e.stopPropagation()}> + setHeaderShow(open) + }} + menu={{ + data: [ + { + key: "export", + label: "导出", + itemIcon: , + children: [ + {key: "encryption", label: "加密导出"}, + {key: "plaintext", label: "明文导出"} + ] + }, + { + key: "edit", + label: "编辑", + disabled: latestProject?.ProjectName === "[default]", + itemIcon: ( + + ) + }, + { + key: "copyPath", + label: "复制路径", + itemIcon: ( + + ) + }, + {type: "divider"}, + { + key: "delete", + label: "删除", + disabled: latestProject?.ProjectName === "[default]", + itemIcon: } - }} + ], + className: styles["dropdown-menu-body"], + popupClassName: styles["dropdown-menu-filter-wrapper"], + onClick: ({key}) => { + setHeaderShow(false) + if (key === "delete") { + if (latestProject) { + setDelId({Id: +latestProject.Id, Type: latestProject.Type}) + setDelShow(true) + } + } else operateFunc(key, latestProject) + } + }} + > +
-
- -
- -
+ +
+
+
{/* )} */}
+
+
+
+ + 临时项目 +
+
+ +
+
+
+
operateFunc("newProject")} @@ -1093,20 +1121,20 @@ const ProjectManage: React.FC = memo((props) => {
{/* { engineMode !== "remote" && ( */} -
operateFunc("import")} - > -
-
- - 导入 -
-
- -
+
operateFunc("import")} + > +
+
+ + 导入 +
+
+
+
{/* )} */}
@@ -1274,9 +1302,7 @@ const ProjectManage: React.FC = memo((props) => { })} onClick={(e) => { if (!i.data.Type || i.data.Type === "project") { - setTimeout(() => { - projectContextMenu(i.data) - }, 100) + operateFunc("setCurrent", i.data) } if (i.data.Type === "file") { operateFunc("openFile", i.data) @@ -1316,9 +1342,9 @@ const ProjectManage: React.FC = memo((props) => {
{/* { engineMode !== "remote" && ( */} -
- {projectOperate(i.data)} -
+
+ {projectOperate(i.data)} +
{/* )} */}
) @@ -1370,6 +1396,35 @@ const ProjectManage: React.FC = memo((props) => { onOk={() => delProjectFolder(false)} onCancel={() => delProjectFolder(true)} /> + + { + setLoading(true) + setInquireIntoProjectVisible(false) + ipcRenderer + .invoke("SetCurrentProject", {Id: newProjectInfo?.Id}) + .then((e) => { + info("已切换数据库") + setNewProjectInfo({Id: "", ProjectName: ""}) + onFinish() + }) + .catch((e) => { + failed("切换数据库失败:" + `${e}`) + }) + .finally(() => { + setTimeout(() => { + setLoading(false) + }, 500) + }) + }} + onCancel={() => { + setNewProjectInfo({Id: "", ProjectName: ""}) + setInquireIntoProjectVisible(false) + }} + />
) }) @@ -1619,7 +1674,8 @@ export const NewProjectAndFolder: React.FC = memo((pro } } } - onModalSubmit(isFolder ? "isNewFolder" : "isNewProject", {...data}) + const type = isFolder ? "isNewFolder" : "isNewProject" + onModalSubmit(type, {...data}) } if (isExport) { if (!exportInfo.Id) { diff --git a/app/renderer/src/main/src/pages/softwareSettings/icon.tsx b/app/renderer/src/main/src/pages/softwareSettings/icon.tsx index fd4102b296..dc71be68a5 100644 --- a/app/renderer/src/main/src/pages/softwareSettings/icon.tsx +++ b/app/renderer/src/main/src/pages/softwareSettings/icon.tsx @@ -212,3 +212,18 @@ const SoftwareRemoteSvg = () => ( export const SoftwareRemoteSvgIcon = (props: Partial) => { return } + +const TemporaryProjectSvg = () => ( + + + +) +/** @name 临时项目图标 */ +export const TemporaryProjectSvgIcon = (props: Partial) => { + return +} diff --git a/app/renderer/src/main/src/store/temporaryProject.tsx b/app/renderer/src/main/src/store/temporaryProject.tsx new file mode 100644 index 0000000000..95494d45bf --- /dev/null +++ b/app/renderer/src/main/src/store/temporaryProject.tsx @@ -0,0 +1,30 @@ +import { getRemoteValue, setRemoteValue } from "@/utils/kv" +import { RemoteGV } from "@/yakitGV" +import {create} from "zustand" +import {persist} from "zustand/middleware" + +interface TemporaryProjectStoreProps { + temporaryProjectId: string + temporaryProjectNoPromptFlag: boolean + setTemporaryProjectId: (id: string) => void + setTemporaryProjectNoPromptFlag: (flag: boolean) => void +} + +export const useTemporaryProjectStore = create()( + persist( + (set, get) => ({ + temporaryProjectId: "", + temporaryProjectNoPromptFlag: false, + setTemporaryProjectId: (id: string) => { + set({temporaryProjectId: id}) + setRemoteValue(RemoteGV.TemporaryProjectId, id) + }, + setTemporaryProjectNoPromptFlag: (flag: boolean) => { + set({temporaryProjectNoPromptFlag: flag}) + } + }), + { + name: "temporary-project" + } + ) +) diff --git a/app/renderer/src/main/src/utils/InvalidCacheAndUserData.tsx b/app/renderer/src/main/src/utils/InvalidCacheAndUserData.tsx index 72937739fd..3ecb3e6c6b 100644 --- a/app/renderer/src/main/src/utils/InvalidCacheAndUserData.tsx +++ b/app/renderer/src/main/src/utils/InvalidCacheAndUserData.tsx @@ -3,18 +3,28 @@ import {info} from "@/utils/notification"; import {showModal} from "@/utils/showModal"; import {Alert, Button, Space} from "antd"; import { getReleaseEditionName } from "./envfile"; +import emiter from "./eventBus/eventBus"; const {ipcRenderer} = window.require("electron"); -export const invalidCacheAndUserData = () => { +export const invalidCacheAndUserData = (temporaryProjectId: string, setTemporaryProjectId) => { + const handleTemporaryProject = async () => { + if (temporaryProjectId) { + await ipcRenderer.invoke("DeleteProject", {Id: +temporaryProjectId, IsDeleteLocal: true}) + setTemporaryProjectId("") + emiter.emit("onFeachGetCurrentProject") + } + } + const m = showModal({ title: "重置用户数据与缓存", content: ( -