├── .gitignore ├── LICENSE ├── README.md ├── README_zh-cn.md ├── backend ├── .gitignore ├── api │ ├── middleware │ │ ├── jwt │ │ │ └── jwt.go │ │ └── license │ │ │ └── license.go │ ├── msg │ │ └── msg.go │ └── routers │ │ ├── data.go │ │ ├── route.go │ │ ├── statick_embed.go │ │ ├── ui.go │ │ └── v2 │ │ ├── auth │ │ └── auth.go │ │ ├── investigation │ │ └── search │ │ │ └── search.go │ │ ├── risk │ │ ├── bash │ │ │ └── bash.go │ │ ├── http_parse │ │ │ └── http_parse.go │ │ ├── pcapanalyse │ │ │ └── pa.go │ │ ├── risk.go │ │ ├── sqli │ │ │ └── sqli.go │ │ ├── webshell │ │ │ └── webshell.go │ │ └── xss │ │ │ └── xss.go │ │ ├── stores │ │ ├── payload │ │ │ └── payload.go │ │ ├── pcap │ │ │ └── pcap.go │ │ └── webshell │ │ │ └── webshell.go │ │ ├── system │ │ └── system.go │ │ └── tools │ │ └── plugin │ │ └── plugin.go ├── build_linux.bat ├── build_mac.bat ├── build_win.bat ├── config.yaml.sample ├── engine │ ├── engine.go │ ├── engine_test.go │ ├── local │ │ ├── http_parse │ │ │ ├── common.go │ │ │ ├── embed.go │ │ │ ├── http_Req_parse.go │ │ │ ├── http_Res_parse.go │ │ │ ├── http_parse.go │ │ │ ├── resource │ │ │ │ ├── dynamic_page_suffix.csv │ │ │ │ ├── file_type_magic_offset.csv │ │ │ │ ├── http_content_type.csv │ │ │ │ └── static_page_suffix.csv │ │ │ └── ua_type.go │ │ ├── sqli │ │ │ ├── sqli.go │ │ │ └── sqli_test.go │ │ └── xss │ │ │ ├── xss.go │ │ │ └── xss_test.go │ ├── online │ │ ├── bash │ │ │ └── bash.go │ │ └── pcapanalyse │ │ │ └── pa.go │ ├── plugin │ │ ├── SerializationDumper │ │ │ └── SerializationDumper.go │ │ ├── jq │ │ │ └── jq.go │ │ ├── pcap │ │ │ └── pcap.go │ │ ├── plugin.go │ │ └── plugin_test.go │ └── workflow │ │ └── workflow.go ├── entry │ └── btab.go ├── go.mod ├── go.sum ├── lib │ ├── embed.go │ ├── java_src │ │ └── SerializationDumper.jar │ └── library.go ├── main.go ├── pack.bat ├── pkg │ ├── conf │ │ ├── config.go │ │ └── default.go │ ├── db │ │ ├── auth.go │ │ ├── conn.go │ │ ├── risk_bash.go │ │ ├── risk_pa.go │ │ ├── risk_sec_type.go │ │ ├── risk_sqli.go │ │ ├── risk_strategy.go │ │ ├── risk_webshell.go │ │ ├── risk_xss.go │ │ ├── stores_payload.go │ │ ├── stores_pcap.go │ │ ├── stores_webshell.go │ │ └── tools_plugin.go │ ├── file │ │ └── file.go │ ├── logging │ │ └── log.go │ ├── net │ │ └── net.go │ ├── pcap │ │ ├── pcap.go │ │ └── pcap_test.go │ ├── proto │ │ ├── README.md │ │ ├── btab.proto │ │ ├── engines.proto │ │ ├── msg.proto │ │ ├── pb │ │ │ ├── btab.pb.go │ │ │ ├── btab_grpc.pb.go │ │ │ ├── btab_pb2.py │ │ │ ├── btab_pb2_grpc.py │ │ │ ├── engines.pb.go │ │ │ ├── engines_grpc.pb.go │ │ │ ├── engines_pb2.py │ │ │ ├── engines_pb2_grpc.py │ │ │ ├── msg.pb.go │ │ │ ├── msg_pb2.py │ │ │ ├── msg_pb2_grpc.py │ │ │ ├── search.pb.go │ │ │ ├── search_grpc.pb.go │ │ │ ├── search_pb2.py │ │ │ └── search_pb2_grpc.py │ │ ├── proto.go │ │ ├── search.proto │ │ ├── search_pb2.py │ │ └── search_pb2_grpc.py │ └── util │ │ ├── helper.go │ │ ├── jwt.go │ │ ├── process.go │ │ ├── rand.go │ │ ├── tcp.go │ │ ├── util.go │ │ └── version.go ├── stores │ └── pcap │ │ ├── Ladon_ms17010_存在漏洞.pcap │ │ ├── log4j_test.pcap │ │ ├── log4j_test2.pcap │ │ └── sqlinjection_9.pcap ├── test │ └── test1 │ │ └── pcap.go └── web │ ├── bindata.go │ ├── cyberchef │ ├── ChefWorker.js.LICENSE.txt │ ├── DishWorker.js.LICENSE.txt │ ├── InputWorker.js.LICENSE.txt │ ├── LoaderWorker.js.LICENSE.txt │ ├── ZipWorker.js.LICENSE.txt │ ├── assets │ │ ├── aecc661b69309290f600.ico │ │ ├── cff684e59ffb052d72cb.woff2 │ │ ├── fonts │ │ │ ├── Roboto72White.fnt │ │ │ ├── Roboto72White.png │ │ │ ├── RobotoBlack72White.fnt │ │ │ ├── RobotoBlack72White.png │ │ │ ├── RobotoMono72White.fnt │ │ │ ├── RobotoMono72White.png │ │ │ ├── RobotoSlab72White.fnt │ │ │ └── RobotoSlab72White.png │ │ ├── forge │ │ │ └── prime.worker.min.js │ │ ├── main.css │ │ ├── main.js │ │ ├── main.js.LICENSE.txt │ │ └── tesseract │ │ │ ├── lang-data │ │ │ └── eng.traineddata.gz │ │ │ ├── tesseract-core.wasm.js │ │ │ ├── worker.min.js │ │ │ └── worker.min.js.LICENSE.txt │ ├── images │ │ ├── cook_male-32x32.png │ │ ├── cyberchef-128x128.png │ │ ├── file-128x128.png │ │ ├── file-32x32.png │ │ └── fork_me.png │ ├── index.html │ └── modules │ │ ├── Bletchley.js │ │ ├── Bletchley.js.LICENSE.txt │ │ ├── Charts.js │ │ ├── Charts.js.LICENSE.txt │ │ ├── Ciphers.js │ │ ├── Ciphers.js.LICENSE.txt │ │ ├── Code.js │ │ ├── Code.js.LICENSE.txt │ │ ├── Compression.js │ │ ├── Compression.js.LICENSE.txt │ │ ├── Crypto.js │ │ ├── Crypto.js.LICENSE.txt │ │ ├── Diff.js │ │ ├── Diff.js.LICENSE.txt │ │ ├── Encodings.js │ │ ├── Encodings.js.LICENSE.txt │ │ ├── Hashing.js │ │ ├── Hashing.js.LICENSE.txt │ │ ├── Image.js │ │ ├── Image.js.LICENSE.txt │ │ ├── OCR.js │ │ ├── OCR.js.LICENSE.txt │ │ ├── PGP.js │ │ ├── PGP.js.LICENSE.txt │ │ ├── Protobuf.js │ │ ├── Protobuf.js.LICENSE.txt │ │ ├── PublicKey.js │ │ ├── PublicKey.js.LICENSE.txt │ │ ├── Regex.js │ │ ├── Regex.js.LICENSE.txt │ │ ├── Serialise.js │ │ ├── Serialise.js.LICENSE.txt │ │ ├── Shellcode.js │ │ ├── Shellcode.js.LICENSE.txt │ │ ├── URL.js │ │ ├── URL.js.LICENSE.txt │ │ ├── UserAgent.js │ │ ├── UserAgent.js.LICENSE.txt │ │ ├── Yara.js │ │ └── Yara.js.LICENSE.txt │ └── embed.go ├── btab蓝队分析工具箱-ali0th-v1.0.pdf ├── frontend ├── .editorconfig ├── .env.development ├── .env.production ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .stylelintignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── index.html ├── mock │ ├── _createProductionServer.ts │ ├── _util.ts │ ├── dashboard │ │ └── console.ts │ ├── system │ │ ├── menu.ts │ │ └── role.ts │ ├── table │ │ └── list.ts │ └── user │ │ ├── menus.ts │ │ └── user.ts ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── public │ └── logo.ico ├── src │ ├── App.vue │ ├── api │ │ ├── dashboard │ │ │ └── console.ts │ │ ├── system │ │ │ ├── menu.ts │ │ │ ├── role.ts │ │ │ ├── system.ts │ │ │ └── user.ts │ │ └── table │ │ │ ├── detail.ts │ │ │ └── list.ts │ ├── assets │ │ ├── cyberchef │ │ │ ├── ChefWorker.js.LICENSE.txt │ │ │ ├── DishWorker.js.LICENSE.txt │ │ │ ├── InputWorker.js.LICENSE.txt │ │ │ ├── LoaderWorker.js.LICENSE.txt │ │ │ ├── ZipWorker.js.LICENSE.txt │ │ │ ├── assets │ │ │ │ ├── aecc661b69309290f600.ico │ │ │ │ ├── cff684e59ffb052d72cb.woff2 │ │ │ │ ├── fonts │ │ │ │ │ ├── Roboto72White.fnt │ │ │ │ │ ├── Roboto72White.png │ │ │ │ │ ├── RobotoBlack72White.fnt │ │ │ │ │ ├── RobotoBlack72White.png │ │ │ │ │ ├── RobotoMono72White.fnt │ │ │ │ │ ├── RobotoMono72White.png │ │ │ │ │ ├── RobotoSlab72White.fnt │ │ │ │ │ └── RobotoSlab72White.png │ │ │ │ ├── forge │ │ │ │ │ └── prime.worker.min.js │ │ │ │ ├── main.css │ │ │ │ ├── main.js │ │ │ │ ├── main.js.LICENSE.txt │ │ │ │ └── tesseract │ │ │ │ │ ├── lang-data │ │ │ │ │ └── eng.traineddata.gz │ │ │ │ │ ├── tesseract-core.wasm.js │ │ │ │ │ ├── worker.min.js │ │ │ │ │ └── worker.min.js.LICENSE.txt │ │ │ ├── images │ │ │ │ ├── cook_male-32x32.png │ │ │ │ ├── cyberchef-128x128.png │ │ │ │ ├── file-128x128.png │ │ │ │ ├── file-32x32.png │ │ │ │ └── fork_me.png │ │ │ ├── index.html │ │ │ └── modules │ │ │ │ ├── Bletchley.js │ │ │ │ ├── Bletchley.js.LICENSE.txt │ │ │ │ ├── Charts.js │ │ │ │ ├── Charts.js.LICENSE.txt │ │ │ │ ├── Ciphers.js │ │ │ │ ├── Ciphers.js.LICENSE.txt │ │ │ │ ├── Code.js │ │ │ │ ├── Code.js.LICENSE.txt │ │ │ │ ├── Compression.js │ │ │ │ ├── Compression.js.LICENSE.txt │ │ │ │ ├── Crypto.js │ │ │ │ ├── Crypto.js.LICENSE.txt │ │ │ │ ├── Diff.js │ │ │ │ ├── Diff.js.LICENSE.txt │ │ │ │ ├── Encodings.js │ │ │ │ ├── Encodings.js.LICENSE.txt │ │ │ │ ├── Hashing.js │ │ │ │ ├── Hashing.js.LICENSE.txt │ │ │ │ ├── Image.js │ │ │ │ ├── Image.js.LICENSE.txt │ │ │ │ ├── OCR.js │ │ │ │ ├── OCR.js.LICENSE.txt │ │ │ │ ├── PGP.js │ │ │ │ ├── PGP.js.LICENSE.txt │ │ │ │ ├── Protobuf.js │ │ │ │ ├── Protobuf.js.LICENSE.txt │ │ │ │ ├── PublicKey.js │ │ │ │ ├── PublicKey.js.LICENSE.txt │ │ │ │ ├── Regex.js │ │ │ │ ├── Regex.js.LICENSE.txt │ │ │ │ ├── Serialise.js │ │ │ │ ├── Serialise.js.LICENSE.txt │ │ │ │ ├── Shellcode.js │ │ │ │ ├── Shellcode.js.LICENSE.txt │ │ │ │ ├── URL.js │ │ │ │ ├── URL.js.LICENSE.txt │ │ │ │ ├── UserAgent.js │ │ │ │ ├── UserAgent.js.LICENSE.txt │ │ │ │ ├── Yara.js │ │ │ │ └── Yara.js.LICENSE.txt │ │ ├── icons │ │ │ ├── login.svg │ │ │ └── logo.svg │ │ └── images │ │ │ ├── Business.svg │ │ │ ├── Error.svg │ │ │ ├── account-logo.png │ │ │ ├── analysis.svg │ │ │ ├── bakup │ │ │ ├── Business.svg │ │ │ ├── Error.svg │ │ │ ├── Snipaste_2022-10-19_09-56-18.png │ │ │ ├── account-logo.png │ │ │ ├── analysis.svg │ │ │ ├── exception │ │ │ │ ├── 403.svg │ │ │ │ ├── 404.svg │ │ │ │ ├── 500.svg │ │ │ │ ├── developing.svg │ │ │ │ ├── load-error.svg │ │ │ │ └── nodata.svg │ │ │ ├── header-theme-dark.svg │ │ │ ├── login.svg │ │ │ ├── logo.png │ │ │ ├── nav-horizontal-mix.svg │ │ │ ├── nav-horizontal.svg │ │ │ ├── nav-theme-dark.svg │ │ │ ├── nav-theme-light.svg │ │ │ ├── schoolboy.png │ │ │ └── tool.png │ │ │ ├── exception │ │ │ ├── 403.svg │ │ │ ├── 404.svg │ │ │ ├── 500.svg │ │ │ ├── developing.svg │ │ │ ├── load-error.svg │ │ │ └── nodata.svg │ │ │ ├── head │ │ │ ├── alarm-clock.png │ │ │ ├── alarm.png │ │ │ ├── alert.png │ │ │ ├── broken-shield.png │ │ │ ├── caution.png │ │ │ ├── cloud.png │ │ │ ├── computer.png │ │ │ ├── cyber-attack.png │ │ │ ├── ddos.png │ │ │ ├── hacked.png │ │ │ ├── hacker.png │ │ │ ├── infection.png │ │ │ ├── malware.png │ │ │ ├── phishing.png │ │ │ ├── problem.png │ │ │ ├── programmer.png │ │ │ ├── spy-bot.png │ │ │ ├── team.png │ │ │ ├── trojan-horse.png │ │ │ └── warning.png │ │ │ ├── header-theme-dark.svg │ │ │ ├── login.svg │ │ │ ├── logo.png │ │ │ ├── nav-horizontal-mix.svg │ │ │ ├── nav-horizontal.svg │ │ │ ├── nav-theme-dark.svg │ │ │ ├── nav-theme-light.svg │ │ │ ├── pay │ │ │ ├── ali0th.jpg │ │ │ ├── antpay.jpg │ │ │ ├── btabGroup.jpg │ │ │ └── wechatpay.jpg │ │ │ ├── schoolboy.png │ │ │ └── tool.png │ ├── components │ │ ├── Application │ │ │ ├── Application.vue │ │ │ └── index.ts │ │ ├── CountTo │ │ │ ├── CountTo.vue │ │ │ └── index.ts │ │ ├── DialogContent │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── Form │ │ │ ├── index.ts │ │ │ └── src │ │ │ │ ├── BasicForm.vue │ │ │ │ ├── helper.ts │ │ │ │ ├── hooks │ │ │ │ ├── useForm.ts │ │ │ │ ├── useFormContext.ts │ │ │ │ ├── useFormEvents.ts │ │ │ │ └── useFormValues.ts │ │ │ │ ├── props.ts │ │ │ │ └── types │ │ │ │ ├── form.ts │ │ │ │ └── index.ts │ │ ├── LoadingContent │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── Lockscreen │ │ │ ├── Lockscreen.vue │ │ │ ├── Recharge.vue │ │ │ └── index.ts │ │ ├── MessageContent │ │ │ ├── index.ts │ │ │ └── index.vue │ │ ├── Modal │ │ │ ├── index.ts │ │ │ └── src │ │ │ │ ├── basicModal.vue │ │ │ │ ├── hooks │ │ │ │ └── useModal.ts │ │ │ │ ├── props.ts │ │ │ │ └── type │ │ │ │ └── index.ts │ │ ├── Table │ │ │ ├── index.ts │ │ │ └── src │ │ │ │ ├── Table.vue │ │ │ │ ├── componentMap.ts │ │ │ │ ├── components │ │ │ │ ├── TableAction.vue │ │ │ │ ├── editable │ │ │ │ │ ├── CellComponent.ts │ │ │ │ │ ├── EditableCell.vue │ │ │ │ │ ├── helper.ts │ │ │ │ │ └── index.ts │ │ │ │ └── settings │ │ │ │ │ └── ColumnSetting.vue │ │ │ │ ├── const.ts │ │ │ │ ├── hooks │ │ │ │ ├── useColumns.ts │ │ │ │ ├── useDataSource.ts │ │ │ │ ├── useLoading.ts │ │ │ │ ├── usePagination.ts │ │ │ │ └── useTableContext.ts │ │ │ │ ├── props.ts │ │ │ │ └── types │ │ │ │ ├── componentType.ts │ │ │ │ ├── pagination.ts │ │ │ │ ├── table.ts │ │ │ │ └── tableAction.ts │ │ └── Upload │ │ │ ├── index.ts │ │ │ └── src │ │ │ ├── BasicUpload.vue │ │ │ ├── props.ts │ │ │ └── type │ │ │ └── index.ts │ ├── config │ │ └── website.config.ts │ ├── directives │ │ ├── clickOutside.ts │ │ └── permission.ts │ ├── enums │ │ ├── breakpointEnum.ts │ │ ├── cacheEnum.ts │ │ ├── httpEnum.ts │ │ ├── pageEnum.ts │ │ ├── permissionsEnum.ts │ │ └── roleEnum.ts │ ├── hooks │ │ ├── core │ │ │ └── useTimeout.ts │ │ ├── event │ │ │ ├── useBreakpoint.ts │ │ │ ├── useEventListener.ts │ │ │ └── useWindowSizeFn.ts │ │ ├── index.ts │ │ ├── setting │ │ │ ├── index.ts │ │ │ ├── useDesignSetting.ts │ │ │ └── useProjectSetting.ts │ │ ├── use-async.ts │ │ ├── useBattery.ts │ │ ├── useDomWidth.ts │ │ ├── useOnline.ts │ │ ├── useTime.ts │ │ └── web │ │ │ ├── useECharts.ts │ │ │ ├── usePage.ts │ │ │ └── usePermission.ts │ ├── layout │ │ ├── components │ │ │ ├── Footer │ │ │ │ ├── index.ts │ │ │ │ └── index.vue │ │ │ ├── Header │ │ │ │ ├── ProjectSetting.vue │ │ │ │ ├── components.ts │ │ │ │ ├── index.ts │ │ │ │ └── index.vue │ │ │ ├── Logo │ │ │ │ ├── index.ts │ │ │ │ └── index.vue │ │ │ ├── Main │ │ │ │ ├── index.ts │ │ │ │ └── index.vue │ │ │ ├── Menu │ │ │ │ ├── index.ts │ │ │ │ └── index.vue │ │ │ └── TagsView │ │ │ │ ├── index.ts │ │ │ │ └── index.vue │ │ ├── index.vue │ │ └── parentLayout.vue │ ├── main.ts │ ├── plugins │ │ ├── customComponents.ts │ │ ├── directives.ts │ │ ├── globalMethods.ts │ │ ├── index.ts │ │ └── naive.ts │ ├── router │ │ ├── base.ts │ │ ├── constant.ts │ │ ├── generator-routers.ts │ │ ├── index.ts │ │ ├── modules │ │ │ ├── about.ts │ │ │ ├── comp.ts │ │ │ ├── dashboard.ts │ │ │ ├── exception.ts │ │ │ ├── form.ts │ │ │ ├── frame.ts │ │ │ ├── result.ts │ │ │ ├── risk.ts │ │ │ ├── setting.ts │ │ │ ├── stores.ts │ │ │ ├── system.ts │ │ │ └── tools.ts │ │ ├── router-guards.ts │ │ ├── router-icons.ts │ │ └── types.ts │ ├── settings │ │ ├── animateSetting.ts │ │ ├── componentSetting.ts │ │ ├── designSetting.ts │ │ └── projectSetting.ts │ ├── store │ │ ├── index.ts │ │ ├── modules │ │ │ ├── asyncRoute.ts │ │ │ ├── designSetting.ts │ │ │ ├── index.ts │ │ │ ├── lockscreen.ts │ │ │ ├── projectSetting.ts │ │ │ ├── tabsView.ts │ │ │ └── user.ts │ │ ├── mutation-types.ts │ │ └── types.ts │ ├── styles │ │ ├── common.less │ │ ├── index.less │ │ ├── tailwind.css │ │ ├── transition │ │ │ ├── base.less │ │ │ ├── fade.less │ │ │ ├── index.less │ │ │ ├── scale.less │ │ │ ├── scroll.less │ │ │ ├── slide.less │ │ │ └── zoom.less │ │ └── var.less │ ├── utils │ │ ├── Drag.ts │ │ ├── Storage.ts │ │ ├── browser-type.ts │ │ ├── dateUtil.ts │ │ ├── domUtils.ts │ │ ├── downloadFile.ts │ │ ├── env.ts │ │ ├── http │ │ │ └── axios │ │ │ │ ├── Axios.ts │ │ │ │ ├── axiosCancel.ts │ │ │ │ ├── axiosTransform.ts │ │ │ │ ├── checkStatus.ts │ │ │ │ ├── helper.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── is │ │ │ └── index.ts │ │ ├── lodashChunk.ts │ │ ├── log.ts │ │ ├── propTypes.ts │ │ └── urlUtils.ts │ └── views │ │ ├── about │ │ └── index.vue │ │ ├── comp │ │ ├── drag │ │ │ └── index.vue │ │ ├── form │ │ │ ├── basic.vue │ │ │ └── useForm.vue │ │ ├── modal │ │ │ └── index.vue │ │ ├── richtext │ │ │ └── vue-quill.vue │ │ ├── table │ │ │ ├── CellColumns.ts │ │ │ ├── basic.vue │ │ │ ├── basicColumns.ts │ │ │ ├── editCell.vue │ │ │ ├── editRow.vue │ │ │ └── rowColumns.ts │ │ └── upload │ │ │ └── index.vue │ │ ├── dashboard │ │ ├── console │ │ │ ├── components │ │ │ │ ├── FluxTrend.vue │ │ │ │ ├── Icons.ts │ │ │ │ ├── VisiTab.vue │ │ │ │ ├── VisitAmount.vue │ │ │ │ └── props.ts │ │ │ └── console.vue │ │ ├── monitor │ │ │ └── monitor.vue │ │ └── workplace │ │ │ └── workplace.vue │ │ ├── exception │ │ ├── 403.vue │ │ ├── 404.vue │ │ └── 500.vue │ │ ├── form │ │ ├── basicForm │ │ │ └── index.vue │ │ ├── detail │ │ │ └── index.vue │ │ └── stepForm │ │ │ ├── Step1.vue │ │ │ ├── Step2.vue │ │ │ ├── Step3.vue │ │ │ └── stepForm.vue │ │ ├── frame │ │ └── docs.vue │ │ ├── iframe │ │ └── index.vue │ │ ├── login │ │ └── index.vue │ │ ├── redirect │ │ └── index.vue │ │ ├── result │ │ ├── fail.vue │ │ ├── info.vue │ │ └── success.vue │ │ ├── risk │ │ ├── bash │ │ │ ├── columns.ts │ │ │ └── index.vue │ │ ├── basicList │ │ │ ├── columns.ts │ │ │ ├── index.vue │ │ │ └── info.vue │ │ ├── http_parse │ │ │ ├── .gitkeep │ │ │ ├── columns.ts │ │ │ └── index.vue │ │ ├── paonline │ │ │ ├── columns.ts │ │ │ └── index.vue │ │ ├── sqli │ │ │ ├── columns.ts │ │ │ └── index.vue │ │ ├── webshell │ │ │ ├── columns.ts │ │ │ └── index.vue │ │ └── xss │ │ │ ├── columns.ts │ │ │ └── index.vue │ │ ├── setting │ │ ├── account │ │ │ ├── BasicSetting.vue │ │ │ ├── SafetySetting.vue │ │ │ └── account.vue │ │ └── system │ │ │ ├── BasicSetting.vue │ │ │ ├── EmailSetting.vue │ │ │ ├── RevealSetting.vue │ │ │ └── system.vue │ │ ├── stores │ │ ├── basicList │ │ │ ├── columns.ts │ │ │ ├── index.vue │ │ │ └── info.vue │ │ ├── payloadList │ │ │ ├── columns.ts │ │ │ └── index.vue │ │ ├── pcapList │ │ │ ├── columns.ts │ │ │ └── index.vue │ │ └── webshellList │ │ │ ├── columns.ts │ │ │ └── index.vue │ │ ├── system │ │ ├── menu │ │ │ ├── CreateDrawer.vue │ │ │ └── menu.vue │ │ └── role │ │ │ ├── columns.ts │ │ │ └── role.vue │ │ └── tools │ │ ├── SerializationDumper │ │ ├── columns.ts │ │ └── index.vue │ │ ├── index.vue │ │ ├── jq │ │ ├── columns.ts │ │ └── index.vue │ │ └── tshark │ │ ├── columns.ts │ │ └── index.vue ├── stylelint.config.js ├── tailwind.config.js ├── tsconfig.json ├── types │ ├── config.d.ts │ ├── global.d.ts │ ├── index.d.ts │ ├── modules.d.ts │ └── utils.d.ts ├── vite.config.ts └── yarn.lock └── investigation ├── S2_Log4j检测_beta.ipynb ├── S2_log4j ├── log4j_test.pcap └── log4j_test2.pcap ├── btab.py ├── btab_example.ipynb ├── doc ├── 2022-11-25-20-04-34.png ├── 2022-11-25-20-13-39.png ├── 2022-11-25-21-34-37.png ├── 2022-11-25-21-36-46.png ├── 2022-11-25-21-45-54.png ├── README.md ├── README_zh-cn.md └── Snipaste_2024-07-03_19-18-35.jpg └── rpc ├── __init__.py ├── btab_pb2.py ├── btab_pb2_grpc.py ├── engines_pb2.py ├── engines_pb2_grpc.py ├── msg_pb2.py ├── msg_pb2_grpc.py ├── search_pb2.py └── search_pb2_grpc.py /backend/api/middleware/jwt/jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "github.com/Martin2877/blue-team-box/api/msg" 5 | "github.com/Martin2877/blue-team-box/pkg/util" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | // middleware 12 | func JWT() gin.HandlerFunc { 13 | return func(c *gin.Context) { 14 | var code int 15 | var data interface{} 16 | var errStr string 17 | 18 | code = msg.SuccessCode 19 | token := c.Request.Header.Get("Authorization") 20 | 21 | if token == "" { 22 | // 非登录状态 23 | code = msg.ErrCode 24 | errStr = "请登录后操作" 25 | } else { 26 | claims, err := util.ParseToken(token) 27 | if err != nil { 28 | // token 校验不通过 29 | code = msg.ErrCode 30 | errStr = "身份验证失败,请重新登录" 31 | } else if time.Now().Unix() > claims.ExpiresAt { 32 | // token 已过期 33 | code = msg.ErrCode 34 | errStr = "身份信息已过期,请重新登录" 35 | } 36 | } 37 | 38 | if code != msg.SuccessCode { 39 | c.JSON(http.StatusUnauthorized, gin.H{ 40 | "code" : code, 41 | "msg" : errStr, 42 | "data" : data, 43 | }) 44 | c.Abort() 45 | return 46 | } 47 | c.Next() 48 | } 49 | } -------------------------------------------------------------------------------- /backend/api/middleware/license/license.go: -------------------------------------------------------------------------------- 1 | package license 2 | 3 | import ( 4 | "github.com/Martin2877/blue-team-box/api/msg" 5 | "github.com/gin-gonic/gin" 6 | "time" 7 | ) 8 | 9 | const DateLine = "2025-10-01T08:18:46+08:00" 10 | 11 | func License() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | // 获取当前日期 14 | todayStr := time.Now() 15 | dl, err := time.Parse(time.RFC3339, DateLine) 16 | if err != nil { 17 | msg.ResultSelfDefined(c, "日期解析错误") 18 | c.Abort() 19 | return 20 | } 21 | if dl.Before(todayStr) { 22 | msg.ResultSelfDefined(c, "认证已经过期,请下载最新版本") 23 | c.Abort() 24 | return 25 | } 26 | c.Next() 27 | return 28 | } 29 | } 30 | 31 | func GetDateLine(c *gin.Context) { 32 | msg.ResultSuccess(c, DateLine) 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /backend/api/routers/data.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | 4 | var Admininfo = map[string]interface{}{ 5 | "userId": "1", 6 | "username": "admin", 7 | "realName": "Admin", 8 | "avatar": "avatar", 9 | "desc": "manager", 10 | "password": "666666", 11 | "token": "666666", 12 | "permissions": []interface{}{ 13 | map[string]interface{}{ 14 | "label": "主控台", 15 | "value": "dashboard_console", 16 | }, 17 | map[string]interface{}{ 18 | "label": "交易监控", 19 | "value": "dashboard_monitor", 20 | }, 21 | map[string]interface{}{ 22 | "label": "监控", 23 | "value": "dashboard_traderbot", 24 | }, 25 | map[string]interface{}{ 26 | "label": "工作台", 27 | "value": "dashboard_workplace", 28 | }, 29 | map[string]interface{}{ 30 | "label": "基础列表", 31 | "value": "basic_list", 32 | }, 33 | map[string]interface{}{ 34 | "label": "基础列表删除", 35 | "value": "basic_list_delete", 36 | }, 37 | }, 38 | } -------------------------------------------------------------------------------- /backend/api/routers/statick_embed.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/Martin2877/blue-team-box/web" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | ) 8 | 9 | // RegisterWebStatick register web static assets route 10 | func RegisterWebStatick(e *gin.Engine) { 11 | routeWebStatic(e, "/ui/ui", "/index.html", "/favicon.ico", "/logo.png", "/sw.js", "/manifest.json", "/assets/*filepath") 12 | } 13 | 14 | func routeWebStatic(e *gin.Engine, paths ...string) { 15 | staticHandler := http.FileServer(web.NewFileSystem()) 16 | handler := func(c *gin.Context) { 17 | staticHandler.ServeHTTP(c.Writer, c.Request) 18 | } 19 | for _, path := range paths { 20 | e.GET(path, handler) 21 | e.HEAD(path, handler) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/api/routers/ui.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/Martin2877/blue-team-box/web" 5 | assetfs "github.com/elazarl/go-bindata-assetfs" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type binaryFileSystem struct { 11 | fs http.FileSystem 12 | } 13 | 14 | func (b *binaryFileSystem) Open(name string) (http.File, error) { 15 | return b.fs.Open(name) 16 | } 17 | 18 | func (b *binaryFileSystem) Exists(prefix, filepath string) bool { 19 | if p := strings.TrimPrefix(filepath, prefix); len(p) > len(filepath) { 20 | if _, err := b.fs.Open(p); err != nil { 21 | return false 22 | } 23 | return true 24 | } 25 | return false 26 | } 27 | 28 | // BinaryFileSystem ... 29 | func BinaryFileSystem(root string) *binaryFileSystem { 30 | return &binaryFileSystem{ 31 | fs: &assetfs.AssetFS{ 32 | Asset: web.Asset, 33 | AssetDir: web.AssetDir, 34 | AssetInfo: web.AssetInfo, 35 | Prefix: root, 36 | Fallback: "index.html", 37 | }, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/api/routers/v2/investigation/search/search.go: -------------------------------------------------------------------------------- 1 | package search 2 | -------------------------------------------------------------------------------- /backend/api/routers/v2/risk/webshell/webshell.go: -------------------------------------------------------------------------------- 1 | package webshell 2 | 3 | import ( 4 | "github.com/Martin2877/blue-team-box/api/msg" 5 | "github.com/Martin2877/blue-team-box/pkg/db" 6 | "github.com/gin-gonic/gin" 7 | "github.com/unknwon/com" 8 | "math" 9 | ) 10 | 11 | type TableResult struct { 12 | Page int `json:"page"` 13 | PageCount int `json:"pageCount"` 14 | PageSize int `json:"pageSize"` 15 | List []db.RiskWebshell `json:"list"` 16 | } 17 | 18 | func Get(c *gin.Context) { 19 | // 获取整个表 20 | field := db.RiskWebshellSearchField{Search: ""} 21 | 22 | // 分页 23 | page, _ := com.StrTo(c.Query("page")).Int() 24 | pageSize, _ := com.StrTo(c.Query("pageSize")).Int() 25 | 26 | // 查询条件 27 | if arg := c.Query("search"); arg != "" { 28 | field.Search = arg 29 | } 30 | 31 | infos := db.GetRiskWebshells(page, pageSize, &field) 32 | total := int(db.GetRiskWebshellTotal(&field)) 33 | tableResult := TableResult{Page: page, PageSize: pageSize, List: infos} 34 | tableResult.PageCount = int(math.Ceil(float64(total / pageSize))) 35 | 36 | msg.ResultSuccess(c, tableResult) 37 | return 38 | 39 | } 40 | 41 | func Submit(c *gin.Context) { 42 | msg.ResultFailed(c, "开源版本无此功能") 43 | return 44 | } 45 | 46 | type WebshellOnce struct { 47 | Webshell string `json:"webshell"` 48 | } 49 | 50 | func SubmitOnce(c *gin.Context) { 51 | msg.ResultFailed(c, "开源版本无此功能") 52 | return 53 | } 54 | 55 | func Delete(c *gin.Context) { 56 | paJson := db.RiskWebshell{} 57 | err := c.ShouldBindJSON(&paJson) 58 | if err != nil { 59 | msg.ResultFailed(c, "参数不合法:"+err.Error()) 60 | return 61 | } 62 | if db.ExistRiskWebshellById(paJson.Id) { 63 | // 删除数据库 64 | db.DeleteRiskWebshell(paJson.Id) 65 | msg.ResultSuccess(c, "删除成功") 66 | return 67 | } else { 68 | msg.ResultFailed(c, "删除失败") 69 | return 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /backend/api/routers/v2/system/system.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "github.com/Martin2877/blue-team-box/api/msg" 5 | "github.com/Martin2877/blue-team-box/pkg/conf" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func GetVersion(c *gin.Context) { 10 | msg.ResultSuccess(c, conf.Version) 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /backend/api/routers/v2/tools/plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Martin2877/blue-team-box/api/msg" 6 | "github.com/Martin2877/blue-team-box/engine/plugin" 7 | "github.com/Martin2877/blue-team-box/pkg/db" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | 12 | type Params struct { 13 | Plugin string `json:"plugin"` 14 | Payloads map[string]string `json:"payloads"` 15 | } 16 | 17 | func SubmitOnce(c *gin.Context) { 18 | paJson := Params{} 19 | err := c.ShouldBindJSON(&paJson) 20 | if err != nil { 21 | msg.ResultFailed(c,"参数不合法:"+err.Error()) 22 | return 23 | } 24 | fmt.Println(paJson) 25 | 26 | // 初始化插件 27 | plugins := plugin.Plugins{} 28 | err = plugins.Init(paJson.Plugin) 29 | if err != nil { 30 | msg.ResultFailed(c,err.Error()) 31 | return 32 | } 33 | 34 | // 插件示例的处理 35 | plugins.Plugin.Init() 36 | for k,v := range paJson.Payloads{ 37 | plugins.Plugin.Set(k,v) 38 | } 39 | err = plugins.Plugin.Check() 40 | if err != nil { 41 | msg.ResultFailed(c,err.Error()) 42 | return 43 | } 44 | // 执行 45 | err = plugins.Plugin.Exec() 46 | if err != nil { 47 | msg.ResultFailed(c,err.Error()) 48 | return 49 | } 50 | if plugins.Plugin.GetState() == db.StateFinish{ 51 | // 返回结果 52 | jres := msg.JsonResponse{ 53 | Code: 20000, 54 | Message: "执行成功", 55 | Result: plugins.Plugin.GetResult(), 56 | Type: "success", 57 | } 58 | msg.ResultSuccess(c,jres) 59 | return 60 | } else { 61 | // 返回结果 62 | jres := msg.JsonResponse{ 63 | Code: 40400, 64 | Message: "执行失败", 65 | Result: plugins.Plugin.GetResult(), 66 | Type: "failed", 67 | } 68 | msg.ResultSuccess(c,jres) 69 | return 70 | } 71 | } 72 | 73 | 74 | -------------------------------------------------------------------------------- /backend/build_linux.bat: -------------------------------------------------------------------------------- 1 | SET CGO_ENABLED=0 2 | SET GOOS=linux 3 | SET GOARCH=amd64 4 | go build -o btab_linux_amd64 -------------------------------------------------------------------------------- /backend/build_mac.bat: -------------------------------------------------------------------------------- 1 | SET CGO_ENABLED=0 2 | SET GOOS=darwin 3 | SET GOARCH=amd64 4 | go build -o btab_darwin_amd64 -------------------------------------------------------------------------------- /backend/build_win.bat: -------------------------------------------------------------------------------- 1 | SET CGO_ENABLED=0 2 | SET GOOS=windows 3 | SET GOARCH=amd64 4 | go build -o btab_windows_amd64.exe -------------------------------------------------------------------------------- /backend/config.yaml.sample: -------------------------------------------------------------------------------- 1 | dbConfig: 2 | enabledefault: false 3 | # mysql: 4 | # database: btab 5 | # host: 127.0.0.1 6 | # password: "" 7 | # port: "3306" 8 | # timeout: 3s 9 | # user: root 10 | sqlite: btab.sqlite 11 | httpConfig: 12 | dail_timeout: 5 13 | headers: 14 | user_agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0 15 | http_timeout: 10 16 | max_qps: 100 17 | max_redirect: 5 18 | proxy: "" 19 | # proxy: "socks5://127.0.0.1:3210" # 使用代理 20 | udp_timeout: 5 21 | cache: False # 是否使用缓存 22 | logConfig: 23 | compress: false 24 | max_age: 365 25 | max_backups: 1 26 | max_size: 50 27 | serverConfig: 28 | jwt_secret: btab 29 | run_mode: release 30 | open_browser: true # 启动时自动打开浏览器 31 | engineConfig: 32 | webshell_host: "http://localhost:8080" 33 | pcapanalyse_host: "http://localhost:5000" 34 | bash_host: "http://localhost:8899" 35 | grpcConfig: 36 | address: :50051 37 | websocketConfig: 38 | address: :5003 39 | pcapAnalyseConfig: 40 | # tsharkPath: tshark # unix、 mac 下使用 41 | tsharkPath: C:\Program Files\Wireshark\tshark.exe # win 下使用 42 | 43 | -------------------------------------------------------------------------------- /backend/engine/engine_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestNewEngines(t *testing.T) { 9 | var err error 10 | var engines = NewEngines() 11 | engines.ClearSteps() 12 | content := ` 13 | : a ' union select concat(md5(2001427499))# 14 | : b { "foo": { "bar": { "baz": 123 } } , "boo":"123"} 15 | : c { "foo": { "bar": { "baz": "' union select concat(md5(2001427499))#" } } , "boo":"123"} 16 | 17 | | jq 18 | |: filter .foo.bar 19 | |: content {{c}} 20 | 21 | | jq 22 | |: filter .foo.bar 23 | |: content {{b}} 24 | 25 | | jq 26 | |: filter .baz 27 | |: content {{R[0]}} 28 | 29 | 30 | | sqli 31 | |: content {{R}} 32 | ` 33 | err = engines.LoadQueries(content) 34 | err = engines.Run() 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | for i := 1; i <= len(engines.Steps); i++ { 39 | log.Println(i-1, " ->", engines.GetResult(i-1)) 40 | } 41 | log.Println("final: ", engines.GetFinalResult()) 42 | } 43 | -------------------------------------------------------------------------------- /backend/engine/local/http_parse/embed.go: -------------------------------------------------------------------------------- 1 | package httpparse 2 | 3 | import "embed" 4 | 5 | //go:embed resource/static_page_suffix.csv 6 | //go:embed resource/dynamic_page_suffix.csv 7 | //go:embed resource/http_content_type.csv 8 | //go:embed resource/file_type_magic_offset.csv 9 | var resource embed.FS 10 | -------------------------------------------------------------------------------- /backend/engine/local/http_parse/http_parse.go: -------------------------------------------------------------------------------- 1 | package httpparse 2 | 3 | /* 4 | 请求和响应的格式必须满足标准格式 5 | */ 6 | 7 | type HTTP_PARSE struct { 8 | } 9 | 10 | // 前端提交过来的请求和响应 11 | // func (http_parse *HTTP_PARSE) Get_Tips(request_data string) (*msg.JsonResponse, error) { 12 | 13 | // // request_datas := strings.Split(request_data, "\n") 14 | 15 | // // 返回结果 16 | // jres := msg.JsonResponse{ 17 | // Code: 20000, 18 | // Message: "发现XSS", 19 | // Result: fmt.Sprintln(request_data), 20 | // Type: "success", 21 | // } 22 | 23 | // return &jres, nil 24 | // } 25 | -------------------------------------------------------------------------------- /backend/engine/local/http_parse/resource/dynamic_page_suffix.csv: -------------------------------------------------------------------------------- 1 | php,php 2 | jsp,jsp 3 | asp,asp 4 | action,java -------------------------------------------------------------------------------- /backend/engine/local/http_parse/resource/file_type_magic_offset.csv: -------------------------------------------------------------------------------- 1 | png,89504E470D0A1A0A,0 2 | pdf,255044462D,0 3 | doc,D0CF11E0A1B11AE1,0 4 | xls,D0CF11E0A1B11AE1,0 5 | ppt,D0CF11E0A1B11AE1,0 6 | msi,D0CF11E0A1B11AE1,0 7 | msg,D0CF11E0A1B11AE1,0 8 | gif,474946383761,0 9 | gif,474946383961,0 -------------------------------------------------------------------------------- /backend/engine/local/http_parse/resource/http_content_type.csv: -------------------------------------------------------------------------------- 1 | application/json,json 2 | application/pdf,pdf 3 | application/octet-stream,file 4 | multipart/form-data,upload_file 5 | application/x-www-form-urlencoded,form_kv 6 | text/css,css 7 | text/xml,xml 8 | application/xml,xml 9 | text/plain,text 10 | application/x-javascript,js 11 | application/javascript,js 12 | text/html,html 13 | image/jpeg,jpg 14 | image/png,png 15 | image/gif,gif 16 | image/svg+xml,svg -------------------------------------------------------------------------------- /backend/engine/local/http_parse/resource/static_page_suffix.csv: -------------------------------------------------------------------------------- 1 | js,js 2 | css,css 3 | png,png 4 | ico,ico 5 | svg,svg 6 | txt,txt 7 | jpg,jpg -------------------------------------------------------------------------------- /backend/engine/local/sqli/sqli_test.go: -------------------------------------------------------------------------------- 1 | package sqli 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSqli_SubmitOnce(t *testing.T) { 9 | cases := []struct { 10 | Name string 11 | Code int 12 | result string 13 | }{ 14 | {") or true--\",\"' or 'x'='x\",\"'", 2000, ""}, 15 | } 16 | 17 | sqli := Sqli{} 18 | for _, c := range cases { 19 | t.Run("pos", func(t *testing.T) { 20 | once, err := sqli.SubmitOnce(c.Name) 21 | if err != nil { 22 | return 23 | } 24 | fmt.Print(once.Result) 25 | if once.Code != c.Code { 26 | t.Fatal("fail") 27 | } 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /backend/engine/local/xss/xss.go: -------------------------------------------------------------------------------- 1 | package xss 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Martin2877/blue-team-box/api/msg" 6 | "github.com/corazawaf/libinjection-go" 7 | ) 8 | 9 | 10 | type Result struct { 11 | Found bool `json:"found"` 12 | Result string `json:"result"` 13 | Catch []string `json:"catch"` 14 | NoCatch []string `json:"no_catch"` 15 | } 16 | 17 | type XSS struct { 18 | } 19 | 20 | func (xss *XSS)SubmitLines(contents []string)(*msg.JsonResponse,error){ 21 | res := Result{} 22 | for _,content := range contents{ 23 | result := libinjection.IsXSS(content) 24 | if result{ 25 | res.Catch = append(res.Catch, content) 26 | }else{ 27 | res.NoCatch = append(res.NoCatch, content) 28 | } 29 | } 30 | if len(res.Catch) > 0{ 31 | res.Found = true 32 | res.Result = fmt.Sprintf("存在XSS,总数 %d 条, 检出 %d 条,未检出 %d 条", len(res.Catch)+len(res.NoCatch), len(res.Catch), len(res.NoCatch)) 33 | jres := msg.JsonResponse{ 34 | Code: 20000, 35 | Message: "", 36 | Result: res, 37 | Type: "success", 38 | } 39 | return &jres,nil 40 | }else{ 41 | res.Found = false 42 | res.Result = "未发现XSS" 43 | jres := msg.JsonResponse{ 44 | Code: 40400, 45 | Message: "", 46 | Result: res, 47 | Type: "success", 48 | } 49 | return &jres,nil 50 | } 51 | } 52 | 53 | 54 | func (xss *XSS) SubmitOnce(content string) (*msg.JsonResponse,error){ 55 | result := libinjection.IsXSS(content) 56 | 57 | if result{ 58 | // 返回结果 59 | jres := msg.JsonResponse{ 60 | Code: 20000, 61 | Message: "发现XSS", 62 | Result: fmt.Sprintln("检测到 XSS"), 63 | Type: "success", 64 | } 65 | return &jres,nil 66 | }else{ 67 | jres := msg.JsonResponse{ 68 | Code: 40400, 69 | Message: "未检测到XSS", 70 | Result: fmt.Sprintln("未发现 XSS"), 71 | Type: "success", 72 | } 73 | return &jres,nil 74 | } 75 | } -------------------------------------------------------------------------------- /backend/engine/local/xss/xss_test.go: -------------------------------------------------------------------------------- 1 | package xss 2 | 3 | import "testing" 4 | 5 | func TestXSS_SubmitOnce(t *testing.T) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /backend/engine/plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "errors" 5 | "github.com/Martin2877/blue-team-box/engine/plugin/SerializationDumper" 6 | "github.com/Martin2877/blue-team-box/engine/plugin/jq" 7 | "github.com/Martin2877/blue-team-box/engine/plugin/pcap" 8 | ) 9 | 10 | type Plugin interface { 11 | Init() 12 | Set(key string, value interface{}) 13 | Check() error 14 | Exec() error 15 | GetState() int 16 | GetFinalStatus() int 17 | GetResult() string 18 | } 19 | 20 | var PluginMap = make(map[string]Plugin) 21 | 22 | func init() { 23 | // 初始化内容 24 | // .. 在这里补充 25 | PluginMap["jq"] = &jq.JQ{} 26 | PluginMap["SerializationDumper"] = &SerializationDumper.SerializationDumper{} 27 | PluginMap["pcap"] = &pcap.Pcap{} 28 | } 29 | 30 | type Plugins struct { 31 | Plugin Plugin `json:"plugin"` 32 | } 33 | 34 | func (plugins *Plugins) Init(plugin string) error { 35 | value, ok := PluginMap[plugin] 36 | if ok { 37 | plugins.Plugin = value 38 | return nil 39 | } 40 | return errors.New("找不到相应插件") 41 | } 42 | -------------------------------------------------------------------------------- /backend/engine/workflow/workflow.go: -------------------------------------------------------------------------------- 1 | package workflow 2 | -------------------------------------------------------------------------------- /backend/lib/embed.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import "embed" 4 | 5 | //go:embed java_src/SerializationDumper.jar 6 | var libResource embed.FS 7 | -------------------------------------------------------------------------------- /backend/lib/java_src/SerializationDumper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/lib/java_src/SerializationDumper.jar -------------------------------------------------------------------------------- /backend/lib/library.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "github.com/kluctl/go-embed-python/embed_util" 5 | "log" 6 | "path" 7 | ) 8 | 9 | const ( 10 | SerializationDumperJar = "SerializationDumper.jar" 11 | ) 12 | 13 | type Library struct { 14 | librarySrc *embed_util.EmbeddedFiles 15 | } 16 | 17 | func (ins *Library) GetSerializationDumperJar() (_path string, err error) { 18 | ins.librarySrc, err = embed_util.NewEmbeddedFiles(libResource, SerializationDumperJar) 19 | if err != nil { 20 | return 21 | } 22 | _path = path.Join(ins.librarySrc.GetExtractedPath(), "java_src", SerializationDumperJar) 23 | return 24 | } 25 | 26 | func (ins *Library) Cleanup() { 27 | err := ins.librarySrc.Cleanup() 28 | if err != nil { 29 | log.Fatalln("清理缓存失败") 30 | return 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/Martin2877/blue-team-box/entry" 5 | ) 6 | 7 | // @title BTAB Api 8 | // @version v0.4.0 9 | // @description BTAB Api 10 | 11 | // @securityDefinitions.apikey token 12 | // @in header 13 | // @name Authorization 14 | 15 | func main() { 16 | entry.RunApp() 17 | } 18 | -------------------------------------------------------------------------------- /backend/pack.bat: -------------------------------------------------------------------------------- 1 | echo auto pack 2 | 3 | echo frontend 4 | cd ..\frontend\ 5 | yarn build 6 | cd ..\backend\ 7 | 8 | xcopy /QEY ..\frontend\dist\ .\web\dist\ 9 | xcopy /QEY .\web\cyberchef\ .\web\dist\assets\cyberchef\ 10 | go-bindata-assetfs -o web/bindata.go -pkg web web/dist/... 11 | 12 | 13 | echo backend 14 | go build -------------------------------------------------------------------------------- /backend/pkg/db/auth.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "gorm.io/gorm" 4 | 5 | // auths 表 6 | 7 | type Auth struct { 8 | gorm.Model 9 | Id int `gorm:"primary_key" json:"id"` 10 | Username string `json:"username"` 11 | Password string `json:"password"` 12 | } 13 | 14 | // CheckAuth checks if authentication information exists 15 | func CheckAuth(username, password string) (bool, *Auth) { 16 | var auth Auth 17 | GlobalDB.Select("id").Where(Auth{Username : username, Password : password}).First(&auth) 18 | if auth.Id > 0 { 19 | return true, &auth 20 | } 21 | return false, nil 22 | } 23 | 24 | // ResetPassword 25 | func ResetPassword(id int, newpassword string) { 26 | var auth Auth 27 | GlobalDB.Model(auth).Where(Auth{Id : id}).Update("password",newpassword) 28 | } -------------------------------------------------------------------------------- /backend/pkg/db/tools_plugin.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | 4 | const ( 5 | StateFree = 0 6 | StateRunning = 1 7 | StatePaused = 2 8 | StateStopped = 3 9 | StateFinish = 4 10 | StateFailed = 5 11 | ) 12 | 13 | const ( 14 | FinalStatusSuccess = 1 15 | FinalStatusFailed = 2 16 | ) 17 | -------------------------------------------------------------------------------- /backend/pkg/net/net.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | ) 7 | 8 | func GetOutBoundIP() (ip string, err error) { 9 | conn, err := net.Dial("udp", "8.8.8.8:53") 10 | if err != nil { 11 | return 12 | } 13 | localAddr := conn.LocalAddr().(*net.UDPAddr) 14 | ip = strings.Split(localAddr.String(), ":")[0] 15 | return 16 | } 17 | -------------------------------------------------------------------------------- /backend/pkg/pcap/pcap_test.go: -------------------------------------------------------------------------------- 1 | package pcap 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/Martin2877/blue-team-box/pkg/conf" 7 | "github.com/tidwall/gjson" 8 | "testing" 9 | ) 10 | 11 | func TestPcaper(t *testing.T) { 12 | conf.Setup() 13 | conf.GlobalConfig.PcapAnalyseConfig.TsharkPath = "F:\\1_program\\60_wireshark\\Wireshark\\tshark.exe" 14 | f := "zabbix_unauthCVE-2022-23131.pcapng" 15 | pcaper := CreatePcaper() 16 | err := pcaper.Load(f) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | pcaper.SetFields([]string{"_ws.col.Time", "ip.src", "tcp.srcport", "ip.dst", "tcp.dstport", "http.request.uri", "http.cookie"}) 21 | query, _, err := pcaper.Query("http.request.method == GET") 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | var result []interface{} 26 | layers := gjson.GetBytes(query, "#._source.layers") 27 | for _, layer := range layers.Array() { 28 | result = append(result, layer.String()) 29 | } 30 | //fmt.Println(result) 31 | 32 | resultJsonByte, _ := json.Marshal(result) 33 | fmt.Println(string(resultJsonByte)) 34 | } 35 | -------------------------------------------------------------------------------- /backend/pkg/proto/btab.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rpc; 3 | 4 | option go_package = "./"; 5 | 6 | import 'msg.proto'; 7 | 8 | // The btab service definition. 9 | service BTAB { 10 | // Sends a greeting 11 | rpc Ping (PingRequest) returns (PingReply) {} 12 | } 13 | 14 | // The request message containing the user's name. 15 | message PingRequest { 16 | } 17 | 18 | // The response message containing the greetings 19 | message PingReply { 20 | ResponseType message = 1; 21 | } 22 | -------------------------------------------------------------------------------- /backend/pkg/proto/engines.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rpc; 3 | option go_package = "./"; 4 | 5 | import 'msg.proto'; 6 | 7 | // The greeting service definition. 8 | service Engines { 9 | rpc CheckAlive (CheckAliveRequest) returns (CheckAliveReply) {} 10 | rpc Set (SetRequest) returns (SetReply) {} 11 | rpc Get (GetRequest) returns (GetReply) {} 12 | rpc Run (RunRequest) returns (RunReply) {} 13 | } 14 | 15 | // The response message containing the greetings 16 | message CheckAliveRequest { 17 | 18 | } 19 | 20 | // The response message containing the greetings 21 | message CheckAliveReply { 22 | ResponseType message = 1; 23 | } 24 | 25 | // The request message containing the user's name. 26 | message SetRequest { 27 | string name = 1; 28 | string content = 2; 29 | } 30 | 31 | // The response message containing the greetings 32 | message SetReply { 33 | ResponseType message = 1; 34 | } 35 | 36 | 37 | // The request message containing the user's name. 38 | message GetRequest { 39 | string name = 1; 40 | } 41 | 42 | // The response message containing the greetings 43 | message GetReply { 44 | ResponseType message = 1; 45 | } 46 | 47 | 48 | // The request message containing the user's name. 49 | message RunRequest { 50 | string content = 1; 51 | } 52 | 53 | // The response message containing the greetings 54 | message RunReply { 55 | ResponseType message = 1; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /backend/pkg/proto/msg.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rpc; 3 | 4 | option go_package = "./"; 5 | 6 | // API Response 基础序列化器 7 | message ResponseType { 8 | int64 Code = 1; 9 | string Message = 2; 10 | string Result = 3; 11 | string Type = 4; 12 | } 13 | -------------------------------------------------------------------------------- /backend/pkg/proto/pb/msg_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: msg.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import descriptor_pool as _descriptor_pool 7 | from google.protobuf import message as _message 8 | from google.protobuf import reflection as _reflection 9 | from google.protobuf import symbol_database as _symbol_database 10 | # @@protoc_insertion_point(imports) 11 | 12 | _sym_db = _symbol_database.Default() 13 | 14 | 15 | 16 | 17 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tmsg.proto\x12\x03rpc\"K\n\x0cResponseType\x12\x0c\n\x04\x43ode\x18\x01 \x01(\x03\x12\x0f\n\x07Message\x18\x02 \x01(\t\x12\x0e\n\x06Result\x18\x03 \x01(\t\x12\x0c\n\x04Type\x18\x04 \x01(\tB\x04Z\x02./b\x06proto3') 18 | 19 | 20 | 21 | _RESPONSETYPE = DESCRIPTOR.message_types_by_name['ResponseType'] 22 | ResponseType = _reflection.GeneratedProtocolMessageType('ResponseType', (_message.Message,), { 23 | 'DESCRIPTOR' : _RESPONSETYPE, 24 | '__module__' : 'msg_pb2' 25 | # @@protoc_insertion_point(class_scope:rpc.ResponseType) 26 | }) 27 | _sym_db.RegisterMessage(ResponseType) 28 | 29 | if _descriptor._USE_C_DESCRIPTORS == False: 30 | 31 | DESCRIPTOR._options = None 32 | DESCRIPTOR._serialized_options = b'Z\002./' 33 | _RESPONSETYPE._serialized_start=18 34 | _RESPONSETYPE._serialized_end=93 35 | # @@protoc_insertion_point(module_scope) 36 | -------------------------------------------------------------------------------- /backend/pkg/proto/pb/msg_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | -------------------------------------------------------------------------------- /backend/pkg/util/helper.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func DeleteSliceItem(nums []string, num string) []string { 4 | result := make([]string, 0, len(nums)) 5 | for _, v := range nums { 6 | if v != num { 7 | result = append(result, v) 8 | } 9 | } 10 | return result 11 | } 12 | 13 | 14 | func SliceContains(arr []string , keyword string) bool{ 15 | for _,a := range arr{ 16 | if a == keyword { 17 | return true 18 | } 19 | } 20 | return false 21 | } 22 | 23 | func SliceBoolContains(arr []bool , keyword bool) bool{ 24 | for _,a := range arr{ 25 | if a == keyword { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | 32 | 33 | 34 | func RemoveDuplicateElement(languages []string) []string { 35 | result := make([]string, 0, len(languages)) 36 | temp := map[string]struct{}{} 37 | for _, item := range languages { 38 | if _, ok := temp[item]; !ok { 39 | temp[item] = struct{}{} 40 | result = append(result, item) 41 | } 42 | } 43 | return result 44 | } 45 | -------------------------------------------------------------------------------- /backend/pkg/util/jwt.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "github.com/dgrijalva/jwt-go" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | var jwtSecret []byte 11 | 12 | type Claims struct { 13 | Username string `json:"username"` 14 | Password string `json:"password"` 15 | jwt.StandardClaims 16 | } 17 | 18 | func GenerateToken(username, password string) (string, error) { 19 | nowTime := time.Now() 20 | // 过期时间 21 | expireTime := nowTime.Add(3 * time.Hour) 22 | 23 | claims := Claims{ 24 | username, 25 | password, 26 | jwt.StandardClaims{ 27 | ExpiresAt: expireTime.Unix(), 28 | Issuer: "btab", 29 | }, 30 | } 31 | // sha256 32 | tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 33 | token, err := tokenClaims.SignedString(jwtSecret) 34 | return token, err 35 | } 36 | 37 | func ParseToken(authHeader string) (*Claims, error) { 38 | // token 前缀为 "JWT" 39 | // 按空格分割 40 | parts := strings.SplitN(authHeader, " ", 2) 41 | if !(len(parts) == 2 && parts[0] == "JWT") { 42 | return nil, errors.New("请求头中auth格式不正确") 43 | } 44 | token := parts[1] 45 | tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { 46 | return jwtSecret, nil 47 | }) 48 | 49 | if tokenClaims != nil { 50 | if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { 51 | return claims, nil 52 | } 53 | } 54 | 55 | return nil, err 56 | } 57 | -------------------------------------------------------------------------------- /backend/pkg/util/process.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | ) 9 | 10 | // SetupCloseHandler 在一个新的 goroutine 上创建一个监听器。 11 | // 如果接收到了一个 interrupt 信号,就会立即通知程序,做一些清理工作并退出 12 | func SetupCloseHandler() { 13 | c := make(chan os.Signal, 2) 14 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 15 | go func() { 16 | <-c 17 | fmt.Println("\r- Ctrl+C pressed in Terminal") 18 | os.Exit(0) 19 | }() 20 | } 21 | -------------------------------------------------------------------------------- /backend/pkg/util/tcp.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/tls" 5 | "github.com/Martin2877/blue-team-box/pkg/conf" 6 | "net" 7 | "time" 8 | ) 9 | // TcpSend 指定目标发送tcp报文,返回结果(仅适用于一次交互即可判断漏洞的场景) 10 | func TcpSend(targetAddr string, data []byte) ([]byte, error) { 11 | tcpTimeout := time.Duration(conf.GlobalConfig.HttpConfig.DailTimeout) * time.Second 12 | conn, err := net.DialTimeout("tcp", targetAddr, tcpTimeout) 13 | if err != nil { 14 | return nil, err 15 | } 16 | defer conn.Close() 17 | conn.SetDeadline(time.Now().Add(tcpTimeout)) 18 | 19 | _, err = conn.Write(data) 20 | if err != nil { 21 | return nil, err 22 | } 23 | buf := make([]byte, 20480) 24 | n, err := conn.Read(buf) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return buf[:n], nil 29 | } 30 | 31 | 32 | // TcpSend 指定目标发送tls嵌套的tcp报文,返回结果(仅适用于一次交互即可判断漏洞的场景) 33 | func TcpTlsSend(targetAddr string, data []byte) ([]byte, error) { 34 | tcpTimeout := time.Duration(conf.GlobalConfig.HttpConfig.HttpTimeout) * time.Second 35 | conn, err := net.DialTimeout("tcp", targetAddr, tcpTimeout) 36 | // add tls 37 | conn = net.Conn(tls.Client(conn, &tls.Config{InsecureSkipVerify: true})) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer conn.Close() 42 | _, err = conn.Write(data) 43 | if err != nil { 44 | return nil, err 45 | } 46 | buf := make([]byte, 20480) 47 | n, err := conn.Read(buf) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return buf[:n], nil 52 | } -------------------------------------------------------------------------------- /backend/pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/tls" 5 | conf2 "github.com/Martin2877/blue-team-box/pkg/conf" 6 | log "github.com/Martin2877/blue-team-box/pkg/logging" 7 | "github.com/valyala/fasthttp" 8 | "github.com/valyala/fasthttp/fasthttpproxy" 9 | ) 10 | 11 | func Setup() { 12 | // fasthttp client 初始化 13 | DownProxy := conf2.GlobalConfig.HttpConfig.Proxy 14 | client := &fasthttp.Client{ 15 | // If InsecureSkipVerify is true, TLS accepts any certificate 16 | TLSConfig: &tls.Config{InsecureSkipVerify: true}, 17 | NoDefaultUserAgentHeader: true, 18 | DisablePathNormalizing: true, 19 | } 20 | if DownProxy != "" { 21 | log.Info("[fasthttp client use proxy ]", DownProxy) 22 | client.Dial = fasthttpproxy.FasthttpHTTPDialer(DownProxy) 23 | } 24 | 25 | // jwt secret 初始化 26 | jwtSecret = []byte(conf2.GlobalConfig.ServerConfig.JwtSecret) 27 | } -------------------------------------------------------------------------------- /backend/pkg/util/version.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // SingleVersionCompare 版本对比方法 9 | // <=0没有漏洞 >0有漏洞 10 | func SingleVersionCompare(verCurrent string, verVul string) (int, error) { 11 | partsCurrent := strings.Split(verCurrent, ".") 12 | partsVul := strings.Split(verVul, ".") 13 | // 比较长度 取短的一方 14 | var parts []string 15 | if len(partsCurrent) < len(partsVul) { 16 | //return 0, errors.New("two version have different parts length") 17 | parts = partsCurrent 18 | } else { 19 | parts = partsVul 20 | } 21 | for i := range parts { 22 | partCurrent, err1 := strconv.Atoi(partsCurrent[i]) 23 | partVul, err2 := strconv.Atoi(partsVul[i]) 24 | if err1 != nil { 25 | return -2, err1 26 | } 27 | if err2 != nil { 28 | return -2, err2 29 | } 30 | if partCurrent == partVul { 31 | continue 32 | } else { 33 | return partVul-partCurrent, nil 34 | } 35 | } 36 | return 0, nil 37 | } -------------------------------------------------------------------------------- /backend/stores/pcap/Ladon_ms17010_存在漏洞.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/stores/pcap/Ladon_ms17010_存在漏洞.pcap -------------------------------------------------------------------------------- /backend/stores/pcap/log4j_test.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/stores/pcap/log4j_test.pcap -------------------------------------------------------------------------------- /backend/stores/pcap/log4j_test2.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/stores/pcap/log4j_test2.pcap -------------------------------------------------------------------------------- /backend/stores/pcap/sqlinjection_9.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/stores/pcap/sqlinjection_9.pcap -------------------------------------------------------------------------------- /backend/test/test1/pcap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | 8 | "os/exec" 9 | ) 10 | 11 | func main() { 12 | f := "1.cap" 13 | tsharkPath := "F:\\1_program\\60_wireshark\\Wireshark\\tshark.exe" 14 | args := []string{"-r", f, "-T", "json"} 15 | 16 | cmd := exec.Command(tsharkPath, args...) 17 | var stdout, stderr bytes.Buffer 18 | cmd.Stdout = &stdout // 标准输出 19 | cmd.Stderr = &stderr // 标准错误 20 | err := cmd.Run() 21 | outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes()) 22 | fmt.Printf("out:\n%s\nerr:\n%s\n", outStr, errStr) 23 | if err != nil { 24 | log.Fatalf("cmd.Run() failed with %s\n", err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/web/cyberchef/LoaderWorker.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * CyberChef - The Cyber Swiss Army Knife 3 | * 4 | * @copyright Crown Copyright 2016 5 | * @license Apache-2.0 6 | * 7 | * Copyright 2016 Crown Copyright 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | /** 23 | * Web Worker to load large amounts of data without locking up the UI. 24 | * 25 | * @author n1474335 [n1474335@gmail.com] 26 | * @copyright Crown Copyright 2017 27 | * @license Apache-2.0 28 | */ 29 | -------------------------------------------------------------------------------- /backend/web/cyberchef/assets/aecc661b69309290f600.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/assets/aecc661b69309290f600.ico -------------------------------------------------------------------------------- /backend/web/cyberchef/assets/cff684e59ffb052d72cb.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/assets/cff684e59ffb052d72cb.woff2 -------------------------------------------------------------------------------- /backend/web/cyberchef/assets/fonts/Roboto72White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/assets/fonts/Roboto72White.png -------------------------------------------------------------------------------- /backend/web/cyberchef/assets/fonts/RobotoBlack72White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/assets/fonts/RobotoBlack72White.png -------------------------------------------------------------------------------- /backend/web/cyberchef/assets/fonts/RobotoMono72White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/assets/fonts/RobotoMono72White.png -------------------------------------------------------------------------------- /backend/web/cyberchef/assets/fonts/RobotoSlab72White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/assets/fonts/RobotoSlab72White.png -------------------------------------------------------------------------------- /backend/web/cyberchef/assets/tesseract/lang-data/eng.traineddata.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/assets/tesseract/lang-data/eng.traineddata.gz -------------------------------------------------------------------------------- /backend/web/cyberchef/assets/tesseract/worker.min.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! For license information please see worker.min.js.LICENSE.txt */ 2 | -------------------------------------------------------------------------------- /backend/web/cyberchef/images/cook_male-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/images/cook_male-32x32.png -------------------------------------------------------------------------------- /backend/web/cyberchef/images/cyberchef-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/images/cyberchef-128x128.png -------------------------------------------------------------------------------- /backend/web/cyberchef/images/file-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/images/file-128x128.png -------------------------------------------------------------------------------- /backend/web/cyberchef/images/file-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/images/file-32x32.png -------------------------------------------------------------------------------- /backend/web/cyberchef/images/fork_me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/backend/web/cyberchef/images/fork_me.png -------------------------------------------------------------------------------- /backend/web/embed.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "embed" 5 | "io/fs" 6 | "net/http" 7 | ) 8 | 9 | //go:embed dist/* 10 | var files embed.FS 11 | 12 | // NewFileSystem get an embed static assets http.FileSystem instance. 13 | func NewFileSystem() http.FileSystem { 14 | subfs, _ := fs.Sub(files, "dist") 15 | return http.FS(subfs) 16 | } 17 | -------------------------------------------------------------------------------- /btab蓝队分析工具箱-ali0th-v1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/btab蓝队分析工具箱-ali0th-v1.0.pdf -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset=utf-8 5 | end_of_line=LF 6 | insert_final_newline=true 7 | indent_style=space 8 | indent_size=2 9 | max_line_length = 100 10 | 11 | [*.{yml,yaml,json}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [Makefile] 19 | indent_style = tab -------------------------------------------------------------------------------- /frontend/.env.development: -------------------------------------------------------------------------------- 1 | # 只在开发模式中被载入 2 | VITE_PORT = 8001 3 | 4 | # 网站根目录 5 | VITE_PUBLIC_PATH = / 6 | 7 | # 是否开启mock 8 | VITE_USE_MOCK = false 9 | 10 | # 网站前缀 11 | VITE_BASE_URL = / 12 | 13 | # 是否删除console 14 | VITE_DROP_CONSOLE = true 15 | 16 | # 跨域代理,可以配置多个,请注意不要换行 17 | #VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]] 18 | #VITE_PROXY=[["/api","https://naive-ui-admin"]] 19 | 20 | # API 接口地址 21 | VITE_GLOB_API_URL = 22 | 23 | # 图片上传地址 24 | VITE_GLOB_UPLOAD_URL= 25 | 26 | # 图片前缀地址 27 | VITE_GLOB_IMG_URL= 28 | 29 | # 接口前缀 30 | VITE_GLOB_API_URL_PREFIX = /api 31 | -------------------------------------------------------------------------------- /frontend/.env.production: -------------------------------------------------------------------------------- 1 | # 是否开启mock 2 | VITE_USE_MOCK = false 3 | 4 | # 网站根目录 5 | VITE_PUBLIC_PATH = / 6 | 7 | # 网站前缀 8 | VITE_BASE_URL = / 9 | 10 | # 是否删除console 11 | VITE_DROP_CONSOLE = true 12 | 13 | # API 14 | VITE_GLOB_API_URL = 15 | 16 | # 图片上传地址 17 | VITE_GLOB_UPLOAD_URL= 18 | 19 | # 图片前缀地址 20 | VITE_GLOB_IMG_URL= 21 | 22 | # 接口前缀 23 | VITE_GLOB_API_URL_PREFIX = /api 24 | 25 | # 是否启用gzip压缩或brotli压缩 26 | # 可选: gzip | brotli | none 27 | # 如果你需要多种形式,你可以用','来分隔 28 | VITE_BUILD_COMPRESS = 'none' 29 | 30 | # 使用压缩时是否删除原始文件,默认为false 31 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false 32 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | node_modules 3 | *.md 4 | *.woff 5 | *.ttf 6 | .vscode 7 | .idea 8 | dist 9 | /public 10 | /docs 11 | .husky 12 | .local 13 | /bin 14 | Dockerfile 15 | components.d.ts 16 | components.d.ts 17 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | .output.js 4 | /node_modules/** 5 | 6 | **/*.svg 7 | **/*.sh 8 | 9 | /public/* 10 | -------------------------------------------------------------------------------- /frontend/.stylelintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /public/* 3 | public/* 4 | -------------------------------------------------------------------------------- /frontend/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present Naive Ui Admin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignores: [(commit) => commit.includes('init')], 3 | extends: ['@commitlint/config-conventional'], 4 | parserPreset: { 5 | parserOpts: { 6 | headerPattern: /^(\w*|[\u4e00-\u9fa5]*)(?:[\(\(](.*)[\)\)])?[\:\:] (.*)/, 7 | headerCorrespondence: ['type', 'scope', 'subject'], 8 | referenceActions: [ 9 | 'close', 10 | 'closes', 11 | 'closed', 12 | 'fix', 13 | 'fixes', 14 | 'fixed', 15 | 'resolve', 16 | 'resolves', 17 | 'resolved', 18 | ], 19 | issuePrefixes: ['#'], 20 | noteKeywords: ['BREAKING CHANGE'], 21 | fieldPattern: /^-(.*?)-$/, 22 | revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\./, 23 | revertCorrespondence: ['header', 'hash'], 24 | warn() {}, 25 | mergePattern: null, 26 | mergeCorrespondence: null, 27 | }, 28 | }, 29 | rules: { 30 | 'body-leading-blank': [2, 'always'], 31 | 'footer-leading-blank': [1, 'always'], 32 | 'header-max-length': [2, 'always', 108], 33 | 'subject-empty': [2, 'never'], 34 | 'type-empty': [2, 'never'], 35 | 'type-enum': [ 36 | 2, 37 | 'always', 38 | [ 39 | 'feat', 40 | 'fix', 41 | 'perf', 42 | 'style', 43 | 'docs', 44 | 'test', 45 | 'refactor', 46 | 'build', 47 | 'ci', 48 | 'chore', 49 | 'revert', 50 | 'wip', 51 | 'workflow', 52 | 'types', 53 | 'release', 54 | ], 55 | ], 56 | }, 57 | }; 58 | -------------------------------------------------------------------------------- /frontend/mock/_createProductionServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'; 2 | 3 | const modules = import.meta.globEager('./**/*.ts'); 4 | 5 | const mockModules: any[] = []; 6 | Object.keys(modules).forEach((key) => { 7 | if (key.includes('/_')) { 8 | return; 9 | } 10 | mockModules.push(...modules[key].default); 11 | }); 12 | 13 | /** 14 | * Used in a production environment. Need to manually import all modules 15 | */ 16 | export function setupProdMockServer() { 17 | createProdMockServer(mockModules); 18 | } 19 | -------------------------------------------------------------------------------- /frontend/mock/_util.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs'; 2 | 3 | export function resultSuccess(result, { message = 'ok' } = {}) { 4 | return Mock.mock({ 5 | code: 200, 6 | result, 7 | message, 8 | type: 'success', 9 | }); 10 | } 11 | 12 | export function resultPageSuccess( 13 | page: number, 14 | pageSize: number, 15 | list: T[], 16 | { message = 'ok' } = {} 17 | ) { 18 | const pageData = pagination(page, pageSize, list); 19 | 20 | return { 21 | ...resultSuccess({ 22 | page, 23 | pageSize, 24 | pageCount: list.length, 25 | list: pageData, 26 | }), 27 | message, 28 | }; 29 | } 30 | 31 | export function resultError(message = 'Request failed', { code = -1, result = null } = {}) { 32 | return { 33 | code, 34 | result, 35 | message, 36 | type: 'error', 37 | }; 38 | } 39 | 40 | export function pagination(pageNo: number, pageSize: number, array: T[]): T[] { 41 | const offset = (pageNo - 1) * Number(pageSize); 42 | const ret = 43 | offset + Number(pageSize) >= array.length 44 | ? array.slice(offset, array.length) 45 | : array.slice(offset, offset + Number(pageSize)); 46 | return ret; 47 | } 48 | 49 | /** 50 | * @param {Number} times 回调函数需要执行的次数 51 | * @param {Function} callback 回调函数 52 | */ 53 | export function doCustomTimes(times: number, callback: any) { 54 | let i = -1; 55 | while (++i < times) { 56 | callback(i); 57 | } 58 | } 59 | 60 | export interface requestParams { 61 | method: string; 62 | body: any; 63 | headers?: { token?: string }; 64 | query: any; 65 | } 66 | 67 | /** 68 | * @description 本函数用于从request数据中获取token,请根据项目的实际情况修改 69 | * 70 | */ 71 | export function getRequestToken({ headers }: requestParams): string | undefined { 72 | return headers?.token; 73 | } 74 | -------------------------------------------------------------------------------- /frontend/mock/dashboard/console.ts: -------------------------------------------------------------------------------- 1 | import { Random } from 'mockjs'; 2 | import { resultSuccess } from '../_util'; 3 | 4 | const consoleInfo = { 5 | //访问量 6 | visits: { 7 | dayVisits: Random.float(10000, 99999, 2, 2), 8 | rise: Random.float(10, 99), 9 | decline: Random.float(10, 99), 10 | amount: Random.float(99999, 999999, 3, 5), 11 | }, 12 | //销售额 13 | saleroom: { 14 | weekSaleroom: Random.float(10000, 99999, 2, 2), 15 | amount: Random.float(99999, 999999, 2, 2), 16 | degree: Random.float(10, 99), 17 | }, 18 | //订单量 19 | orderLarge: { 20 | weekLarge: Random.float(10000, 99999, 2, 2), 21 | rise: Random.float(10, 99), 22 | decline: Random.float(10, 99), 23 | amount: Random.float(99999, 999999, 2, 2), 24 | }, 25 | //成交额度 26 | volume: { 27 | weekLarge: Random.float(10000, 99999, 2, 2), 28 | rise: Random.float(10, 99), 29 | decline: Random.float(10, 99), 30 | amount: Random.float(99999, 999999, 2, 2), 31 | }, 32 | }; 33 | 34 | export default [ 35 | //主控台 卡片数据 36 | { 37 | url: '/api/dashboard/console', 38 | timeout: 1000, 39 | method: 'get', 40 | response: () => { 41 | return resultSuccess(consoleInfo); 42 | }, 43 | }, 44 | ]; 45 | -------------------------------------------------------------------------------- /frontend/mock/system/role.ts: -------------------------------------------------------------------------------- 1 | import { resultSuccess, doCustomTimes } from '../_util'; 2 | 3 | function getMenuKeys() { 4 | const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail']; 5 | const newKeys = []; 6 | doCustomTimes(parseInt(Math.random() * 6), () => { 7 | const key = keys[Math.floor(Math.random() * keys.length)]; 8 | newKeys.push(key); 9 | }); 10 | return Array.from(new Set(newKeys)); 11 | } 12 | 13 | const roleList = (pageSize) => { 14 | const result: any[] = []; 15 | doCustomTimes(pageSize, () => { 16 | result.push({ 17 | id: '@integer(10,100)', 18 | name: '@cname()', 19 | explain: '@cname()', 20 | isDefault: '@boolean()', 21 | menu_keys: getMenuKeys(), 22 | create_date: `@date('yyyy-MM-dd hh:mm:ss')`, 23 | 'status|1': ['normal', 'enable', 'disable'], 24 | }); 25 | }); 26 | return result; 27 | }; 28 | 29 | export default [ 30 | { 31 | url: '/api/role/list', 32 | timeout: 1000, 33 | method: 'get', 34 | response: ({ query }) => { 35 | const { page = 1, pageSize = 10 } = query; 36 | const list = roleList(Number(pageSize)); 37 | return resultSuccess({ 38 | page: Number(page), 39 | pageSize: Number(pageSize), 40 | pageCount: 60, 41 | list, 42 | }); 43 | }, 44 | }, 45 | ]; 46 | -------------------------------------------------------------------------------- /frontend/mock/table/list.ts: -------------------------------------------------------------------------------- 1 | import { Random } from 'mockjs'; 2 | import { resultSuccess, doCustomTimes } from '../_util'; 3 | 4 | const tableList = (pageSize) => { 5 | const result: any[] = []; 6 | doCustomTimes(pageSize, () => { 7 | result.push({ 8 | id: '@integer(10,999999)', 9 | beginTime: '@datetime', 10 | endTime: '@datetime', 11 | address: '@city()', 12 | name: '@cname()', 13 | avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()), 14 | date: `@date('yyyy-MM-dd')`, 15 | time: `@time('HH:mm')`, 16 | 'no|100000-10000000': 100000, 17 | 'status|1': [true, false], 18 | }); 19 | }); 20 | return result; 21 | }; 22 | 23 | export default [ 24 | //表格数据列表 25 | { 26 | url: '/api/table/list', 27 | timeout: 1000, 28 | method: 'get', 29 | response: ({ query }) => { 30 | const { page = 1, pageSize = 10 } = query; 31 | const list = tableList(Number(pageSize)); 32 | return resultSuccess({ 33 | page: Number(page), 34 | pageSize: Number(pageSize), 35 | pageCount: 60, 36 | list, 37 | }); 38 | }, 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /frontend/mock/user/menus.ts: -------------------------------------------------------------------------------- 1 | import { resultSuccess } from '../_util'; 2 | 3 | const menusList = [ 4 | { 5 | path: '/dashboard', 6 | name: 'Dashboard', 7 | component: 'LAYOUT', 8 | redirect: '/dashboard/console', 9 | meta: { 10 | icon: 'DashboardOutlined', 11 | title: 'Dashboard', 12 | }, 13 | children: [ 14 | { 15 | path: 'console', 16 | name: 'dashboard_console', 17 | component: '/dashboard/console/console', 18 | meta: { 19 | title: '主控台', 20 | }, 21 | }, 22 | { 23 | path: 'monitor', 24 | name: 'dashboard_monitor', 25 | component: '/dashboard/monitor/monitor', 26 | meta: { 27 | title: '监控页', 28 | }, 29 | }, 30 | { 31 | path: 'workplace', 32 | name: 'dashboard_workplace', 33 | component: '/dashboard/workplace/workplace', 34 | meta: { 35 | hidden: true, 36 | title: '工作台', 37 | }, 38 | }, 39 | ], 40 | }, 41 | ]; 42 | 43 | export default [ 44 | { 45 | url: '/api/menus', 46 | timeout: 1000, 47 | method: 'get', 48 | response: () => { 49 | return resultSuccess(menusList); 50 | }, 51 | }, 52 | ]; 53 | -------------------------------------------------------------------------------- /frontend/mock/user/user.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs'; 2 | import { resultSuccess } from '../_util'; 3 | 4 | const Random = Mock.Random; 5 | 6 | const token = Random.string('upper', 32, 32); 7 | 8 | const adminInfo = { 9 | userId: '1', 10 | username: 'admin', 11 | realName: 'Admin', 12 | avatar: Random.image(), 13 | desc: 'manager', 14 | password: Random.string('upper', 4, 16), 15 | token, 16 | permissions: [ 17 | { 18 | label: '主控台', 19 | value: 'dashboard_console', 20 | }, 21 | { 22 | label: '监控页', 23 | value: 'dashboard_monitor', 24 | }, 25 | { 26 | label: '工作台', 27 | value: 'dashboard_workplace', 28 | }, 29 | { 30 | label: '基础列表', 31 | value: 'basic_list', 32 | }, 33 | { 34 | label: '基础列表删除', 35 | value: 'basic_list_delete', 36 | }, 37 | ], 38 | }; 39 | 40 | export default [ 41 | { 42 | url: '/api/login', 43 | timeout: 1000, 44 | method: 'post', 45 | response: () => { 46 | return resultSuccess({ token }); 47 | }, 48 | }, 49 | { 50 | url: '/api/admin_info', 51 | timeout: 1000, 52 | method: 'get', 53 | response: () => { 54 | // const token = getRequestToken(request); 55 | // if (!token) return resultError('Invalid token'); 56 | return resultSuccess(adminInfo); 57 | }, 58 | }, 59 | ]; 60 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | vueIndentScriptAndStyle: true, 7 | singleQuote: true, 8 | quoteProps: 'as-needed', 9 | bracketSpacing: true, 10 | trailingComma: 'es5', 11 | jsxBracketSameLine: false, 12 | jsxSingleQuote: false, 13 | arrowParens: 'always', 14 | insertPragma: false, 15 | requirePragma: false, 16 | proseWrap: 'never', 17 | htmlWhitespaceSensitivity: 'strict', 18 | endOfLine: 'auto', 19 | rangeStart: 0, 20 | }; 21 | -------------------------------------------------------------------------------- /frontend/public/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/public/logo.ico -------------------------------------------------------------------------------- /frontend/src/api/dashboard/console.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | //获取主控台信息 4 | export function getConsoleInfo() { 5 | return http.request({ 6 | url: '/dashboard/console', 7 | method: 'get', 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/api/system/menu.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | /** 4 | * @description: 根据用户id获取用户菜单 5 | */ 6 | export function adminMenus() { 7 | return http.request({ 8 | url: '/menus', 9 | method: 'GET', 10 | }); 11 | } 12 | 13 | /** 14 | * 获取tree菜单列表 15 | * @param params 16 | */ 17 | export function getMenuList(params?) { 18 | return http.request({ 19 | url: '/menu/list', 20 | method: 'GET', 21 | params, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/api/system/role.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | /** 4 | * @description: 角色列表 5 | */ 6 | export function getRoleList() { 7 | return http.request({ 8 | url: '/role/list', 9 | method: 'GET', 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/api/system/system.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | export interface BasicResponseModel { 4 | code: number; 5 | message: string; 6 | result: T; 7 | } 8 | 9 | export interface BasicPageParams { 10 | pageNumber: number; 11 | pageSize: number; 12 | total: number; 13 | } 14 | 15 | /** 16 | * @description: 获取系统版本 17 | */ 18 | export function getSystemVersion() { 19 | return http.request({ 20 | url: '/system/version', 21 | method: 'get', 22 | }); 23 | } 24 | 25 | /** 26 | * @description: 获取认证日期 27 | */ 28 | export function getLicenseDateline() { 29 | return http.request({ 30 | url: '/license/dateline', 31 | method: 'get', 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/api/system/user.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | export interface BasicResponseModel { 4 | code: number; 5 | message: string; 6 | result: T; 7 | } 8 | 9 | export interface BasicPageParams { 10 | pageNumber: number; 11 | pageSize: number; 12 | total: number; 13 | } 14 | 15 | /** 16 | * @description: 获取用户信息 17 | */ 18 | export function getUserInfo() { 19 | return http.request({ 20 | url: '/admin_info', 21 | method: 'get', 22 | }); 23 | } 24 | 25 | /** 26 | * @description: 用户登录 27 | */ 28 | export function login(params) { 29 | return http.request( 30 | { 31 | url: '/login', 32 | method: 'POST', 33 | params, 34 | }, 35 | { 36 | isTransformResponse: false, 37 | } 38 | ); 39 | } 40 | 41 | /** 42 | * @description: 用户修改密码 43 | */ 44 | export function changePassword(params, uid) { 45 | return http.request( 46 | { 47 | url: `/user/u${uid}/changepw`, 48 | method: 'POST', 49 | params, 50 | }, 51 | { 52 | isTransformResponse: false, 53 | } 54 | ); 55 | } 56 | 57 | /** 58 | * @description: 用户登出 59 | */ 60 | export function logout(params) { 61 | return http.request({ 62 | url: '/login/logout', 63 | method: 'POST', 64 | params, 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /frontend/src/api/table/detail.ts: -------------------------------------------------------------------------------- 1 | import { http } from '@/utils/http/axios'; 2 | 3 | 4 | export interface BasicResponseModel { 5 | code: number; 6 | message: string; 7 | result: T; 8 | } 9 | 10 | export interface BasicPageParams { 11 | pageNumber: number; 12 | pageSize: number; 13 | total: number; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/LoaderWorker.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * CyberChef - The Cyber Swiss Army Knife 3 | * 4 | * @copyright Crown Copyright 2016 5 | * @license Apache-2.0 6 | * 7 | * Copyright 2016 Crown Copyright 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | */ 21 | 22 | /** 23 | * Web Worker to load large amounts of data without locking up the UI. 24 | * 25 | * @author n1474335 [n1474335@gmail.com] 26 | * @copyright Crown Copyright 2017 27 | * @license Apache-2.0 28 | */ 29 | -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/assets/aecc661b69309290f600.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/assets/aecc661b69309290f600.ico -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/assets/cff684e59ffb052d72cb.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/assets/cff684e59ffb052d72cb.woff2 -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/assets/fonts/Roboto72White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/assets/fonts/Roboto72White.png -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/assets/fonts/RobotoBlack72White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/assets/fonts/RobotoBlack72White.png -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/assets/fonts/RobotoMono72White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/assets/fonts/RobotoMono72White.png -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/assets/fonts/RobotoSlab72White.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/assets/fonts/RobotoSlab72White.png -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/assets/tesseract/lang-data/eng.traineddata.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/assets/tesseract/lang-data/eng.traineddata.gz -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/assets/tesseract/worker.min.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! For license information please see worker.min.js.LICENSE.txt */ 2 | -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/images/cook_male-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/images/cook_male-32x32.png -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/images/cyberchef-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/images/cyberchef-128x128.png -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/images/file-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/images/file-128x128.png -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/images/file-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/images/file-32x32.png -------------------------------------------------------------------------------- /frontend/src/assets/cyberchef/images/fork_me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/cyberchef/images/fork_me.png -------------------------------------------------------------------------------- /frontend/src/assets/images/account-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/account-logo.png -------------------------------------------------------------------------------- /frontend/src/assets/images/bakup/Snipaste_2022-10-19_09-56-18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/bakup/Snipaste_2022-10-19_09-56-18.png -------------------------------------------------------------------------------- /frontend/src/assets/images/bakup/account-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/bakup/account-logo.png -------------------------------------------------------------------------------- /frontend/src/assets/images/bakup/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/bakup/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/images/bakup/nav-horizontal-mix.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/assets/images/bakup/schoolboy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/bakup/schoolboy.png -------------------------------------------------------------------------------- /frontend/src/assets/images/bakup/tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/bakup/tool.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/alarm-clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/alarm-clock.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/alarm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/alarm.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/alert.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/broken-shield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/broken-shield.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/caution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/caution.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/cloud.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/computer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/computer.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/cyber-attack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/cyber-attack.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/ddos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/ddos.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/hacked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/hacked.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/hacker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/hacker.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/infection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/infection.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/malware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/malware.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/phishing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/phishing.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/problem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/problem.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/programmer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/programmer.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/spy-bot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/spy-bot.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/team.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/trojan-horse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/trojan-horse.png -------------------------------------------------------------------------------- /frontend/src/assets/images/head/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/head/warning.png -------------------------------------------------------------------------------- /frontend/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/images/nav-horizontal-mix.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/assets/images/pay/ali0th.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/pay/ali0th.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/pay/antpay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/pay/antpay.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/pay/btabGroup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/pay/btabGroup.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/pay/wechatpay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/pay/wechatpay.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/schoolboy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/schoolboy.png -------------------------------------------------------------------------------- /frontend/src/assets/images/tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/assets/images/tool.png -------------------------------------------------------------------------------- /frontend/src/components/Application/Application.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 41 | -------------------------------------------------------------------------------- /frontend/src/components/Application/index.ts: -------------------------------------------------------------------------------- 1 | import AppProvider from './Application.vue'; 2 | 3 | export { AppProvider }; 4 | -------------------------------------------------------------------------------- /frontend/src/components/CountTo/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@/utils'; 2 | import countTo from './CountTo.vue'; 3 | 4 | export const CountTo = withInstall(countTo); 5 | -------------------------------------------------------------------------------- /frontend/src/components/DialogContent/index.ts: -------------------------------------------------------------------------------- 1 | import DialogContent from './index.vue'; 2 | 3 | export { DialogContent }; 4 | -------------------------------------------------------------------------------- /frontend/src/components/DialogContent/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /frontend/src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicForm } from './src/BasicForm.vue'; 2 | export { useForm } from './src/hooks/useForm'; 3 | export * from './src/types/form'; 4 | export * from './src/types/index'; 5 | -------------------------------------------------------------------------------- /frontend/src/components/Form/src/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from './types/index'; 2 | 3 | /** 4 | * @description: 生成placeholder 5 | */ 6 | export function createPlaceholderMessage(component: ComponentType) { 7 | if (component === 'NInput') return '请输入'; 8 | if ( 9 | ['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes( 10 | component 11 | ) 12 | ) 13 | return '请选择'; 14 | return ''; 15 | } 16 | 17 | const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker']; 18 | 19 | function genType() { 20 | return [...DATE_TYPE, 'RangePicker']; 21 | } 22 | 23 | /** 24 | * 时间字段 25 | */ 26 | export const dateItemType = genType(); 27 | 28 | export function defaultType(component) { 29 | if (component === 'NInput') return ''; 30 | if (component === 'NInputNumber') return null; 31 | return [ 32 | 'NPicker', 33 | 'NSelect', 34 | 'NCheckbox', 35 | 'NRadio', 36 | 'NSwitch', 37 | 'NDatePicker', 38 | 'NTimePicker', 39 | ].includes(component) 40 | ? '' 41 | : undefined; 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/components/Form/src/hooks/useFormContext.ts: -------------------------------------------------------------------------------- 1 | import { provide, inject } from 'vue'; 2 | 3 | const key = Symbol('formElRef'); 4 | 5 | export function createFormContext(instance) { 6 | provide(key, instance); 7 | } 8 | 9 | export function useFormContext() { 10 | return inject(key); 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/components/Form/src/hooks/useFormValues.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '@/utils/is'; 2 | import { unref } from 'vue'; 3 | import type { Ref, ComputedRef } from 'vue'; 4 | import type { FormSchema } from '../types/form'; 5 | import { set } from 'lodash-es'; 6 | 7 | interface UseFormValuesContext { 8 | defaultFormModel: Ref; 9 | getSchema: ComputedRef; 10 | formModel: Recordable; 11 | } 12 | export function useFormValues({ defaultFormModel, getSchema, formModel }: UseFormValuesContext) { 13 | // 加工 form values 14 | function handleFormValues(values: Recordable) { 15 | if (!isObject(values)) { 16 | return {}; 17 | } 18 | const res: Recordable = {}; 19 | for (const item of Object.entries(values)) { 20 | let [, value] = item; 21 | const [key] = item; 22 | if ( 23 | !key || 24 | (isArray(value) && value.length === 0) || 25 | isFunction(value) || 26 | isNullOrUnDef(value) 27 | ) { 28 | continue; 29 | } 30 | // 删除空格 31 | if (isString(value)) { 32 | value = value.trim(); 33 | } 34 | set(res, key, value); 35 | } 36 | return res; 37 | } 38 | 39 | //初始化默认值 40 | function initDefault() { 41 | const schemas = unref(getSchema); 42 | const obj: Recordable = {}; 43 | schemas.forEach((item) => { 44 | const { defaultValue } = item; 45 | if (!isNullOrUnDef(defaultValue)) { 46 | obj[item.field] = defaultValue; 47 | formModel[item.field] = defaultValue; 48 | } 49 | }); 50 | defaultFormModel.value = obj; 51 | } 52 | 53 | return { handleFormValues, initDefault }; 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/components/Form/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'NInput' 3 | | 'NInputGroup' 4 | | 'NInputPassword' 5 | | 'NInputSearch' 6 | | 'NInputTextArea' 7 | | 'NInputNumber' 8 | | 'NInputCountDown' 9 | | 'NSelect' 10 | | 'NTreeSelect' 11 | | 'NRadioButtonGroup' 12 | | 'NRadioGroup' 13 | | 'NCheckbox' 14 | | 'NCheckboxGroup' 15 | | 'NAutoComplete' 16 | | 'NCascader' 17 | | 'NDatePicker' 18 | | 'NMonthPicker' 19 | | 'NRangePicker' 20 | | 'NWeekPicker' 21 | | 'NTimePicker' 22 | | 'NSwitch' 23 | | 'NStrengthMeter' 24 | | 'NUpload' 25 | | 'NIconPicker' 26 | | 'NRender' 27 | | 'NSlider' 28 | | 'NRate'; 29 | -------------------------------------------------------------------------------- /frontend/src/components/LoadingContent/index.ts: -------------------------------------------------------------------------------- 1 | import LoadingContent from './index.vue'; 2 | 3 | export { LoadingContent }; 4 | -------------------------------------------------------------------------------- /frontend/src/components/LoadingContent/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /frontend/src/components/Lockscreen/index.ts: -------------------------------------------------------------------------------- 1 | import LockScreen from './Lockscreen.vue'; 2 | 3 | export { LockScreen }; 4 | -------------------------------------------------------------------------------- /frontend/src/components/MessageContent/index.ts: -------------------------------------------------------------------------------- 1 | import MessageContent from './index.vue'; 2 | 3 | export { MessageContent }; 4 | -------------------------------------------------------------------------------- /frontend/src/components/MessageContent/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /frontend/src/components/Modal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as basicModal } from './src/basicModal.vue'; 2 | export { useModal } from './src/hooks/useModal'; 3 | export * from './src/type'; 4 | -------------------------------------------------------------------------------- /frontend/src/components/Modal/src/hooks/useModal.ts: -------------------------------------------------------------------------------- 1 | import { ref, unref, getCurrentInstance, watch } from 'vue'; 2 | import { isProdMode } from '@/utils/env'; 3 | import { ModalMethods, UseModalReturnType } from '../type'; 4 | import { getDynamicProps } from '@/utils'; 5 | import { tryOnUnmounted } from '@vueuse/core'; 6 | export function useModal(props): UseModalReturnType { 7 | const modalRef = ref>(null); 8 | const currentInstance = getCurrentInstance(); 9 | 10 | const getInstance = () => { 11 | const instance = unref(modalRef.value); 12 | if (!instance) { 13 | console.error('useModal instance is undefined!'); 14 | } 15 | return instance; 16 | }; 17 | 18 | const register = (modalInstance: ModalMethods) => { 19 | isProdMode() && 20 | tryOnUnmounted(() => { 21 | modalRef.value = null; 22 | }); 23 | modalRef.value = modalInstance; 24 | currentInstance?.emit('register', modalInstance); 25 | 26 | watch( 27 | () => props, 28 | () => { 29 | props && modalInstance.setProps(getDynamicProps(props)); 30 | }, 31 | { 32 | immediate: true, 33 | deep: true, 34 | } 35 | ); 36 | }; 37 | 38 | const methods: ModalMethods = { 39 | setProps: (props): void => { 40 | getInstance()?.setProps(props); 41 | }, 42 | openModal: () => { 43 | getInstance()?.openModal(); 44 | }, 45 | closeModal: () => { 46 | getInstance()?.closeModal(); 47 | }, 48 | setSubLoading: (status) => { 49 | getInstance()?.setSubLoading(status); 50 | }, 51 | }; 52 | 53 | return [register, methods]; 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/components/Modal/src/props.ts: -------------------------------------------------------------------------------- 1 | import { NModal } from 'naive-ui'; 2 | 3 | export const basicProps = { 4 | ...NModal.props, 5 | // 确认按钮文字 6 | subBtuText: { 7 | type: String, 8 | default: '确认', 9 | }, 10 | showIcon: { 11 | type: Boolean, 12 | default: false, 13 | }, 14 | width: { 15 | type: Number, 16 | default: 446, 17 | }, 18 | title: { 19 | type: String, 20 | default: '', 21 | }, 22 | maskClosable: { 23 | type: Boolean, 24 | default: false, 25 | }, 26 | preset: { 27 | type: String, 28 | default: 'dialog', 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/src/components/Modal/src/type/index.ts: -------------------------------------------------------------------------------- 1 | import type { DialogOptions } from 'naive-ui/lib/dialog'; 2 | /** 3 | * @description: 弹窗对外暴露的方法 4 | */ 5 | export interface ModalMethods { 6 | setProps: (props) => void; 7 | openModal: () => void; 8 | closeModal: () => void; 9 | setSubLoading: (status) => void; 10 | } 11 | 12 | /** 13 | * 支持修改,DialogOptions 參數 14 | */ 15 | export type ModalProps = DialogOptions; 16 | 17 | export type RegisterFn = (ModalInstance: ModalMethods) => void; 18 | 19 | export type UseModalReturnType = [RegisterFn, ModalMethods]; 20 | -------------------------------------------------------------------------------- /frontend/src/components/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicTable } from './src/Table.vue'; 2 | export { default as TableAction } from './src/components/TableAction.vue'; 3 | export * from './src/types/table'; 4 | export * from './src/types/tableAction'; 5 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/componentMap.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from 'vue'; 2 | import { 3 | NInput, 4 | NSelect, 5 | NCheckbox, 6 | NInputNumber, 7 | NSwitch, 8 | NDatePicker, 9 | NTimePicker, 10 | } from 'naive-ui'; 11 | import type { ComponentType } from './types/componentType'; 12 | 13 | export enum EventEnum { 14 | NInput = 'on-input', 15 | NInputNumber = 'on-input', 16 | NSelect = 'on-update:value', 17 | NSwitch = 'on-update:value', 18 | NCheckbox = 'on-update:value', 19 | NDatePicker = 'on-update:value', 20 | NTimePicker = 'on-update:value', 21 | } 22 | 23 | const componentMap = new Map(); 24 | 25 | componentMap.set('NInput', NInput); 26 | componentMap.set('NInputNumber', NInputNumber); 27 | componentMap.set('NSelect', NSelect); 28 | componentMap.set('NSwitch', NSwitch); 29 | componentMap.set('NCheckbox', NCheckbox); 30 | componentMap.set('NDatePicker', NDatePicker); 31 | componentMap.set('NTimePicker', NTimePicker); 32 | 33 | export function add(compName: ComponentType, component: Component) { 34 | componentMap.set(compName, component); 35 | } 36 | 37 | export function del(compName: ComponentType) { 38 | componentMap.delete(compName); 39 | } 40 | 41 | export { componentMap }; 42 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/components/editable/CellComponent.ts: -------------------------------------------------------------------------------- 1 | import type { FunctionalComponent, defineComponent } from 'vue'; 2 | import type { ComponentType } from '../../types/componentType'; 3 | import { componentMap } from '@/components/Table/src/componentMap'; 4 | 5 | import { h } from 'vue'; 6 | 7 | import { NPopover } from 'naive-ui'; 8 | 9 | export interface ComponentProps { 10 | component: ComponentType; 11 | rule: boolean; 12 | popoverVisible: boolean; 13 | ruleMessage: string; 14 | } 15 | 16 | export const CellComponent: FunctionalComponent = ( 17 | { component = 'NInput', rule = true, ruleMessage, popoverVisible }: ComponentProps, 18 | { attrs } 19 | ) => { 20 | const Comp = componentMap.get(component) as typeof defineComponent; 21 | 22 | const DefaultComp = h(Comp, attrs); 23 | if (!rule) { 24 | return DefaultComp; 25 | } 26 | return h( 27 | NPopover, 28 | { 'display-directive': 'show', show: !!popoverVisible, manual: 'manual' }, 29 | { 30 | trigger: () => DefaultComp, 31 | default: () => 32 | h( 33 | 'span', 34 | { 35 | style: { 36 | color: 'red', 37 | width: '90px', 38 | display: 'inline-block', 39 | }, 40 | }, 41 | { 42 | default: () => ruleMessage, 43 | } 44 | ), 45 | } 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/components/editable/helper.ts: -------------------------------------------------------------------------------- 1 | import { ComponentType } from '../../types/componentType'; 2 | 3 | /** 4 | * @description: 生成placeholder 5 | */ 6 | export function createPlaceholderMessage(component: ComponentType) { 7 | if (component === 'NInput') return '请输入'; 8 | if ( 9 | ['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes( 10 | component 11 | ) 12 | ) 13 | return '请选择'; 14 | return ''; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/components/editable/index.ts: -------------------------------------------------------------------------------- 1 | import type { BasicColumn } from '@/components/Table/src/types/table'; 2 | import { h, Ref } from 'vue'; 3 | 4 | import EditableCell from './EditableCell.vue'; 5 | 6 | export function renderEditCell(column: BasicColumn) { 7 | return (record, index) => { 8 | const _key = column.key; 9 | const value = record[_key]; 10 | record.onEdit = async (edit: boolean, submit = false) => { 11 | if (!submit) { 12 | record.editable = edit; 13 | } 14 | 15 | if (!edit && submit) { 16 | const res = await record.onSubmitEdit?.(); 17 | if (res) { 18 | record.editable = false; 19 | return true; 20 | } 21 | return false; 22 | } 23 | // cancel 24 | if (!edit && !submit) { 25 | record.onCancelEdit?.(); 26 | } 27 | return true; 28 | }; 29 | return h(EditableCell, { 30 | value, 31 | record, 32 | column, 33 | index, 34 | }); 35 | }; 36 | } 37 | 38 | export type EditRecordRow = Partial< 39 | { 40 | onEdit: (editable: boolean, submit?: boolean) => Promise; 41 | editable: boolean; 42 | onCancel: Fn; 43 | onSubmit: Fn; 44 | submitCbs: Fn[]; 45 | cancelCbs: Fn[]; 46 | validCbs: Fn[]; 47 | editValueRefs: Recordable; 48 | } & T 49 | >; 50 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/const.ts: -------------------------------------------------------------------------------- 1 | import componentSetting from '@/settings/componentSetting'; 2 | 3 | const { table } = componentSetting; 4 | 5 | const { apiSetting, defaultPageSize, pageSizes } = table; 6 | 7 | export const DEFAULTPAGESIZE = defaultPageSize; 8 | 9 | export const APISETTING = apiSetting; 10 | 11 | export const PAGESIZES = pageSizes; 12 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { ref, ComputedRef, unref, computed, watch } from 'vue'; 2 | import type { BasicTableProps } from '../types/table'; 3 | 4 | export function useLoading(props: ComputedRef) { 5 | const loadingRef = ref(unref(props).loading); 6 | 7 | watch( 8 | () => unref(props).loading, 9 | (loading) => { 10 | loadingRef.value = loading; 11 | } 12 | ); 13 | 14 | const getLoading = computed(() => unref(loadingRef)); 15 | 16 | function setLoading(loading: boolean) { 17 | loadingRef.value = loading; 18 | } 19 | 20 | return { getLoading, setLoading }; 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/hooks/usePagination.ts: -------------------------------------------------------------------------------- 1 | import type { PaginationProps } from '../types/pagination'; 2 | import type { BasicTableProps } from '../types/table'; 3 | import { computed, unref, ref, ComputedRef } from 'vue'; 4 | 5 | import { isBoolean } from '@/utils/is'; 6 | import { APISETTING, DEFAULTPAGESIZE, PAGESIZES } from '../const'; 7 | 8 | export function usePagination(refProps: ComputedRef) { 9 | const configRef = ref({}); 10 | const show = ref(true); 11 | 12 | const getPaginationInfo = computed((): PaginationProps | boolean => { 13 | const { pagination } = unref(refProps); 14 | if (!unref(show) || (isBoolean(pagination) && !pagination)) { 15 | return false; 16 | } 17 | const { totalField } = APISETTING; 18 | return { 19 | pageSize: DEFAULTPAGESIZE, 20 | pageSizes: PAGESIZES, 21 | showSizePicker: true, 22 | showQuickJumper: true, 23 | ...(isBoolean(pagination) ? {} : pagination), 24 | ...unref(configRef), 25 | pageCount: unref(configRef)[totalField], 26 | }; 27 | }); 28 | 29 | function setPagination(info: Partial) { 30 | const paginationInfo = unref(getPaginationInfo); 31 | configRef.value = { 32 | ...(!isBoolean(paginationInfo) ? paginationInfo : {}), 33 | ...info, 34 | }; 35 | } 36 | 37 | function getPagination() { 38 | return unref(getPaginationInfo); 39 | } 40 | 41 | function getShowPagination() { 42 | return unref(show); 43 | } 44 | 45 | async function setShowPagination(flag: boolean) { 46 | show.value = flag; 47 | } 48 | 49 | return { getPagination, getPaginationInfo, setShowPagination, getShowPagination, setPagination }; 50 | } 51 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/hooks/useTableContext.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import type { BasicTableProps, TableActionType } from '../types/table'; 3 | import { provide, inject, ComputedRef } from 'vue'; 4 | 5 | const key = Symbol('s-table'); 6 | 7 | type Instance = TableActionType & { 8 | wrapRef: Ref>; 9 | getBindValues: ComputedRef; 10 | }; 11 | 12 | type RetInstance = Omit & { 13 | getBindValues: ComputedRef; 14 | }; 15 | 16 | export function createTableContext(instance: Instance) { 17 | provide(key, instance); 18 | } 19 | 20 | export function useTableContext(): RetInstance { 21 | return inject(key) as RetInstance; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue'; 2 | import { propTypes } from '@/utils/propTypes'; 3 | import { BasicColumn } from './types/table'; 4 | import { NDataTable } from 'naive-ui'; 5 | export const basicProps = { 6 | ...NDataTable.props, // 这里继承原 UI 组件的 props 7 | title: { 8 | type: String, 9 | default: null, 10 | }, 11 | titleTooltip: { 12 | type: String, 13 | default: null, 14 | }, 15 | size: { 16 | type: String, 17 | default: 'medium', 18 | }, 19 | dataSource: { 20 | type: [Object], 21 | default: () => [], 22 | }, 23 | columns: { 24 | type: [Array] as PropType, 25 | default: () => [], 26 | required: true, 27 | }, 28 | beforeRequest: { 29 | type: Function as PropType<(...arg: any[]) => void | Promise>, 30 | default: null, 31 | }, 32 | request: { 33 | type: Function as PropType<(...arg: any[]) => Promise>, 34 | default: null, 35 | }, 36 | afterRequest: { 37 | type: Function as PropType<(...arg: any[]) => void | Promise>, 38 | default: null, 39 | }, 40 | rowKey: { 41 | type: [String, Function] as PropType string)>, 42 | default: undefined, 43 | }, 44 | pagination: { 45 | type: [Object, Boolean], 46 | default: () => {}, 47 | }, 48 | //废弃 49 | showPagination: { 50 | type: [String, Boolean], 51 | default: 'auto', 52 | }, 53 | actionColumn: { 54 | type: Object as PropType, 55 | default: null, 56 | }, 57 | canResize: propTypes.bool.def(true), 58 | resizeHeightOffset: propTypes.number.def(0), 59 | }; 60 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/types/componentType.ts: -------------------------------------------------------------------------------- 1 | export type ComponentType = 2 | | 'NInput' 3 | | 'NInputNumber' 4 | | 'NSelect' 5 | | 'NCheckbox' 6 | | 'NSwitch' 7 | | 'NDatePicker' 8 | | 'NTimePicker'; 9 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/types/pagination.ts: -------------------------------------------------------------------------------- 1 | export interface PaginationProps { 2 | page?: number; 3 | pageCount?: number; 4 | pageSize?: number; 5 | pageSizes?: number[]; 6 | showSizePicker?: boolean; 7 | showQuickJumper?: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/types/table.ts: -------------------------------------------------------------------------------- 1 | import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface'; 2 | import { ComponentType } from './componentType'; 3 | export interface BasicColumn extends TableBaseColumn { 4 | //编辑表格 5 | edit?: boolean; 6 | editRow?: boolean; 7 | editable?: boolean; 8 | editComponent?: ComponentType; 9 | editComponentProps?: Recordable; 10 | editRule?: boolean | ((text: string, record: Recordable) => Promise); 11 | editValueMap?: (value: any) => string; 12 | onEditRow?: () => void; 13 | // 权限编码控制是否显示 14 | auth?: string[]; 15 | // 业务控制是否显示 16 | ifShow?: boolean | ((column: BasicColumn) => boolean); 17 | // 控制是否支持拖拽,默认支持 18 | draggable?: boolean; 19 | } 20 | 21 | export interface TableActionType { 22 | reload: (opt) => Promise; 23 | emit?: any; 24 | getColumns: (opt?) => BasicColumn[]; 25 | setColumns: (columns: BasicColumn[] | string[]) => void; 26 | } 27 | 28 | export interface BasicTableProps { 29 | title?: string; 30 | dataSource: Function; 31 | columns: any[]; 32 | pagination: object; 33 | showPagination: boolean; 34 | actionColumn: any[]; 35 | canResize: boolean; 36 | resizeHeightOffset: number; 37 | loading: boolean; 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/components/Table/src/types/tableAction.ts: -------------------------------------------------------------------------------- 1 | import { NButton } from 'naive-ui'; 2 | import type { Component } from 'vue'; 3 | import { PermissionsEnum } from '@/enums/permissionsEnum'; 4 | export interface ActionItem extends Partial> { 5 | onClick?: Fn; 6 | label?: string; 7 | type?: 'success' | 'error' | 'warning' | 'info' | 'primary' | 'default'; 8 | // 设定 color 后会覆盖 type 的样式 9 | color?: string; 10 | icon?: Component; 11 | popConfirm?: PopConfirm; 12 | disabled?: boolean; 13 | divider?: boolean; 14 | // 权限编码控制是否显示 15 | auth?: PermissionsEnum | PermissionsEnum[] | string | string[]; 16 | // 业务控制是否显示 17 | ifShow?: boolean | ((action: ActionItem) => boolean); 18 | } 19 | 20 | export interface PopConfirm { 21 | title: string; 22 | okText?: string; 23 | cancelText?: string; 24 | confirm: Fn; 25 | cancel?: Fn; 26 | icon?: Component; 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/components/Upload/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BasicUpload } from './src/BasicUpload.vue'; 2 | -------------------------------------------------------------------------------- /frontend/src/components/Upload/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from 'vue'; 2 | import { NUpload } from 'naive-ui'; 3 | 4 | export const basicProps = { 5 | ...NUpload.props, 6 | accept: { 7 | type: String, 8 | default: '.jpg,.png,.jpeg,.svg,.gif', 9 | }, 10 | helpText: { 11 | type: String as PropType, 12 | default: '', 13 | }, 14 | maxSize: { 15 | type: Number as PropType, 16 | default: 2, 17 | }, 18 | maxNumber: { 19 | type: Number as PropType, 20 | default: Infinity, 21 | }, 22 | value: { 23 | type: Array as PropType, 24 | default: () => [], 25 | }, 26 | width: { 27 | type: Number as PropType, 28 | default: 104, 29 | }, 30 | height: { 31 | type: Number as PropType, 32 | default: 104, //建议不小于这个尺寸 太小页面可能显示有异常 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /frontend/src/components/Upload/src/type/index.ts: -------------------------------------------------------------------------------- 1 | export interface BasicProps { 2 | title?: string; 3 | dataSource: Function; 4 | columns: any[]; 5 | pagination: object; 6 | showPagination: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/config/website.config.ts: -------------------------------------------------------------------------------- 1 | import logoImage from '@/assets/images/logo.png'; 2 | import loginImage from '@/assets/images/account-logo.png'; 3 | 4 | export const websiteConfig = Object.freeze({ 5 | title: 'BTAB', 6 | logo: logoImage, 7 | loginImage: loginImage, 8 | loginDesc: 'BTAB: 蓝队专用多功能分析工具箱', 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/src/directives/permission.ts: -------------------------------------------------------------------------------- 1 | import { ObjectDirective } from 'vue'; 2 | import { usePermission } from '@/hooks/web/usePermission'; 3 | 4 | export const permission: ObjectDirective = { 5 | mounted(el: HTMLButtonElement, binding) { 6 | if (binding.value == undefined) return; 7 | const { action, effect } = binding.value; 8 | const { hasPermission } = usePermission(); 9 | if (!hasPermission(action)) { 10 | if (effect == 'disabled') { 11 | el.disabled = true; 12 | el.style['disabled'] = 'disabled'; 13 | el.classList.add('n-button--disabled'); 14 | } else { 15 | el.remove(); 16 | } 17 | } 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/enums/breakpointEnum.ts: -------------------------------------------------------------------------------- 1 | export enum sizeEnum { 2 | XS = 'XS', 3 | SM = 'SM', 4 | MD = 'MD', 5 | LG = 'LG', 6 | XL = 'XL', 7 | XXL = 'XXL', 8 | } 9 | 10 | export enum screenEnum { 11 | XS = 480, 12 | SM = 576, 13 | MD = 768, 14 | LG = 992, 15 | XL = 1200, 16 | XXL = 1600, 17 | } 18 | 19 | const screenMap = new Map(); 20 | 21 | screenMap.set(sizeEnum.XS, screenEnum.XS); 22 | screenMap.set(sizeEnum.SM, screenEnum.SM); 23 | screenMap.set(sizeEnum.MD, screenEnum.MD); 24 | screenMap.set(sizeEnum.LG, screenEnum.LG); 25 | screenMap.set(sizeEnum.XL, screenEnum.XL); 26 | screenMap.set(sizeEnum.XXL, screenEnum.XXL); 27 | 28 | export { screenMap }; 29 | -------------------------------------------------------------------------------- /frontend/src/enums/cacheEnum.ts: -------------------------------------------------------------------------------- 1 | // token key 2 | export const TOKEN_KEY = 'TOKEN'; 3 | 4 | // user info key 5 | export const USER_INFO_KEY = 'USER__INFO__'; 6 | 7 | // role info key 8 | export const ROLES_KEY = 'ROLES__KEY__'; 9 | 10 | // project config key 11 | export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__'; 12 | 13 | // lock info 14 | export const LOCK_INFO_KEY = 'LOCK__INFO__KEY__'; 15 | 16 | // base global local key 17 | export const BASE_LOCAL_CACHE_KEY = 'LOCAL__CACHE__KEY__'; 18 | 19 | // base global session key 20 | export const BASE_SESSION_CACHE_KEY = 'SESSION__CACHE__KEY__'; 21 | -------------------------------------------------------------------------------- /frontend/src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 请求结果集 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 200, 6 | ERROR = -1, 7 | TIMEOUT = 10042, 8 | TYPE = 'success', 9 | } 10 | 11 | /** 12 | * @description: 请求方法 13 | */ 14 | export enum RequestEnum { 15 | GET = 'GET', 16 | POST = 'POST', 17 | PATCH = 'PATCH', 18 | PUT = 'PUT', 19 | DELETE = 'DELETE', 20 | } 21 | 22 | /** 23 | * @description: 常用的contentTyp类型 24 | */ 25 | export enum ContentTypeEnum { 26 | // json 27 | JSON = 'application/json;charset=UTF-8', 28 | // json 29 | TEXT = 'text/plain;charset=UTF-8', 30 | // form-data 一般配合qs 31 | FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', 32 | // form-data 上传 33 | FORM_DATA = 'multipart/form-data;charset=UTF-8', 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/enums/pageEnum.ts: -------------------------------------------------------------------------------- 1 | export enum PageEnum { 2 | // 登录 3 | BASE_LOGIN = '/login', 4 | BASE_LOGIN_NAME = 'Login', 5 | //重定向 6 | REDIRECT = '/redirect', 7 | REDIRECT_NAME = 'Redirect', 8 | // 首页 9 | BASE_HOME = '/stores', 10 | //首页跳转默认路由 11 | BASE_HOME_REDIRECT = '/stores/pcap-list', 12 | // 错误 13 | ERROR_PAGE_NAME = 'ErrorPage', 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/enums/permissionsEnum.ts: -------------------------------------------------------------------------------- 1 | export interface PermissionsEnum { 2 | value: string; 3 | label: string; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/enums/roleEnum.ts: -------------------------------------------------------------------------------- 1 | export enum RoleEnum { 2 | // 管理员 3 | ADMIN = 'admin', 4 | 5 | // 普通用户 6 | NORMAL = 'normal', 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/hooks/core/useTimeout.ts: -------------------------------------------------------------------------------- 1 | import { ref, watch } from 'vue'; 2 | import { tryOnUnmounted } from '@vueuse/core'; 3 | import { isFunction } from '@/utils/is'; 4 | 5 | export function useTimeoutFn(handle: Fn, wait: number, native = false) { 6 | if (!isFunction(handle)) { 7 | throw new Error('handle is not Function!'); 8 | } 9 | 10 | const { readyRef, stop, start } = useTimeoutRef(wait); 11 | if (native) { 12 | handle(); 13 | } else { 14 | watch( 15 | readyRef, 16 | (maturity) => { 17 | maturity && handle(); 18 | }, 19 | { immediate: false } 20 | ); 21 | } 22 | return { readyRef, stop, start }; 23 | } 24 | 25 | export function useTimeoutRef(wait: number) { 26 | const readyRef = ref(false); 27 | 28 | let timer: TimeoutHandle; 29 | 30 | function stop(): void { 31 | readyRef.value = false; 32 | timer && window.clearTimeout(timer); 33 | } 34 | 35 | function start(): void { 36 | stop(); 37 | timer = setTimeout(() => { 38 | readyRef.value = true; 39 | }, wait); 40 | } 41 | 42 | start(); 43 | 44 | tryOnUnmounted(stop); 45 | 46 | return { readyRef, stop, start }; 47 | } 48 | -------------------------------------------------------------------------------- /frontend/src/hooks/event/useWindowSizeFn.ts: -------------------------------------------------------------------------------- 1 | import { tryOnMounted, tryOnUnmounted } from '@vueuse/core'; 2 | import { useDebounceFn } from '@vueuse/core'; 3 | 4 | interface WindowSizeOptions { 5 | once?: boolean; 6 | immediate?: boolean; 7 | listenerOptions?: AddEventListenerOptions | boolean; 8 | } 9 | 10 | export function useWindowSizeFn(fn: Fn, wait = 150, options?: WindowSizeOptions) { 11 | let handler = () => { 12 | fn(); 13 | }; 14 | const handleSize = useDebounceFn(handler, wait); 15 | handler = handleSize; 16 | 17 | const start = () => { 18 | if (options && options.immediate) { 19 | handler(); 20 | } 21 | window.addEventListener('resize', handler); 22 | }; 23 | 24 | const stop = () => { 25 | window.removeEventListener('resize', handler); 26 | }; 27 | 28 | tryOnMounted(() => { 29 | start(); 30 | }); 31 | 32 | tryOnUnmounted(() => { 33 | stop(); 34 | }); 35 | return [start, stop]; 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import { useAsync } from './use-async'; 2 | 3 | export { useAsync }; 4 | -------------------------------------------------------------------------------- /frontend/src/hooks/setting/index.ts: -------------------------------------------------------------------------------- 1 | import type { GlobConfig } from '/#/config'; 2 | 3 | import { warn } from '@/utils/log'; 4 | import { getAppEnvConfig } from '@/utils/env'; 5 | 6 | export const useGlobSetting = (): Readonly => { 7 | const { 8 | VITE_GLOB_APP_TITLE, 9 | VITE_GLOB_API_URL, 10 | VITE_GLOB_APP_SHORT_NAME, 11 | VITE_GLOB_API_URL_PREFIX, 12 | VITE_GLOB_UPLOAD_URL, 13 | VITE_GLOB_PROD_MOCK, 14 | VITE_GLOB_IMG_URL, 15 | } = getAppEnvConfig(); 16 | 17 | if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) { 18 | warn( 19 | `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.` 20 | ); 21 | } 22 | 23 | // Take global configuration 24 | const glob: Readonly = { 25 | title: VITE_GLOB_APP_TITLE, 26 | apiUrl: VITE_GLOB_API_URL, 27 | shortName: VITE_GLOB_APP_SHORT_NAME, 28 | urlPrefix: VITE_GLOB_API_URL_PREFIX, 29 | uploadUrl: VITE_GLOB_UPLOAD_URL, 30 | prodMock: VITE_GLOB_PROD_MOCK, 31 | imgUrl: VITE_GLOB_IMG_URL, 32 | }; 33 | return glob as Readonly; 34 | }; 35 | -------------------------------------------------------------------------------- /frontend/src/hooks/setting/useDesignSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useDesignSettingStore } from '@/store/modules/designSetting'; 3 | 4 | export function useDesignSetting() { 5 | const designStore = useDesignSettingStore(); 6 | 7 | const getDarkTheme = computed(() => designStore.darkTheme); 8 | 9 | const getAppTheme = computed(() => designStore.appTheme); 10 | 11 | const getAppThemeList = computed(() => designStore.appThemeList); 12 | 13 | return { 14 | getDarkTheme, 15 | getAppTheme, 16 | getAppThemeList, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/hooks/setting/useProjectSetting.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useProjectSettingStore } from '@/store/modules/projectSetting'; 3 | 4 | export function useProjectSetting() { 5 | const projectStore = useProjectSettingStore(); 6 | 7 | const getNavMode = computed(() => projectStore.navMode); 8 | 9 | const getNavTheme = computed(() => projectStore.navTheme); 10 | 11 | const getIsMobile = computed(() => projectStore.isMobile); 12 | 13 | const getHeaderSetting = computed(() => projectStore.headerSetting); 14 | 15 | const getMultiTabsSetting = computed(() => projectStore.multiTabsSetting); 16 | 17 | const getMenuSetting = computed(() => projectStore.menuSetting); 18 | 19 | const getCrumbsSetting = computed(() => projectStore.crumbsSetting); 20 | 21 | const getPermissionMode = computed(() => projectStore.permissionMode); 22 | 23 | const getShowFooter = computed(() => projectStore.showFooter); 24 | 25 | const getIsPageAnimate = computed(() => projectStore.isPageAnimate); 26 | 27 | const getPageAnimateType = computed(() => projectStore.pageAnimateType); 28 | 29 | return { 30 | getNavMode, 31 | getNavTheme, 32 | getIsMobile, 33 | getHeaderSetting, 34 | getMultiTabsSetting, 35 | getMenuSetting, 36 | getCrumbsSetting, 37 | getPermissionMode, 38 | getShowFooter, 39 | getIsPageAnimate, 40 | getPageAnimateType, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/hooks/use-async.ts: -------------------------------------------------------------------------------- 1 | import { isReactive, isRef } from 'vue'; 2 | 3 | function setLoading(loading, val) { 4 | if (loading != undefined && isRef(loading)) { 5 | loading.value = val; 6 | } else if (loading != undefined && isReactive(loading)) { 7 | loading.loading = val; 8 | } 9 | } 10 | 11 | export const useAsync = async (func: Promise, loading: any): Promise => { 12 | setLoading(loading, true); 13 | 14 | return await func.finally(() => setLoading(loading, false)); 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/src/hooks/useDomWidth.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from 'vue'; 2 | import { debounce } from 'lodash'; 3 | 4 | /** 5 | * description: 获取页面宽度 6 | */ 7 | 8 | export function useDomWidth() { 9 | const domWidth = ref(window.innerWidth); 10 | 11 | function resize() { 12 | domWidth.value = document.body.clientWidth; 13 | } 14 | 15 | onMounted(() => { 16 | window.addEventListener('resize', debounce(resize, 80)); 17 | }); 18 | onUnmounted(() => { 19 | window.removeEventListener('resize', resize); 20 | }); 21 | 22 | return domWidth; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/hooks/useOnline.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from 'vue'; 2 | 3 | /** 4 | * @description 用户网络是否可用 5 | * */ 6 | export function useOnline() { 7 | const online = ref(true); 8 | 9 | const showStatus = (val) => { 10 | online.value = typeof val == 'boolean' ? val : val.target.online; 11 | }; 12 | 13 | // 在页面加载后,设置正确的网络状态 14 | navigator.onLine ? showStatus(true) : showStatus(false); 15 | 16 | onMounted(() => { 17 | // 开始监听网络状态的变化 18 | window.addEventListener('online', showStatus); 19 | 20 | window.addEventListener('offline', showStatus); 21 | }); 22 | onUnmounted(() => { 23 | // 移除监听网络状态的变化 24 | window.removeEventListener('online', showStatus); 25 | 26 | window.removeEventListener('offline', showStatus); 27 | }); 28 | 29 | return { online }; 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/hooks/useTime.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from 'vue'; 2 | 3 | /** 4 | * @description 获取本地时间 5 | */ 6 | export function useTime() { 7 | let timer; // 定时器 8 | const year = ref(0); // 年份 9 | const month = ref(0); // 月份 10 | const week = ref(''); // 星期几 11 | const day = ref(0); // 天数 12 | const hour = ref(0); // 小时 13 | const minute = ref(0); // 分钟 14 | const second = ref(0); // 秒 15 | 16 | // 更新时间 17 | const updateTime = () => { 18 | const date = new Date(); 19 | year.value = date.getFullYear(); 20 | month.value = date.getMonth() + 1; 21 | week.value = '日一二三四五六'.charAt(date.getDay()); 22 | day.value = date.getDate(); 23 | hour.value = 24 | (date.getHours() + '')?.padStart(2, '0') || 25 | new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours()); 26 | minute.value = 27 | (date.getMinutes() + '')?.padStart(2, '0') || 28 | new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes()); 29 | second.value = date.getSeconds(); 30 | }; 31 | 32 | // 原生时间格式化 33 | // new Intl.DateTimeFormat('zh', { 34 | // year: 'numeric', 35 | // month: '2-digit', 36 | // day: '2-digit', 37 | // hour: '2-digit', 38 | // minute: '2-digit', 39 | // second: '2-digit', 40 | // hour12: false 41 | // }).format(new Date()) 42 | 43 | updateTime(); 44 | 45 | onMounted(() => { 46 | clearInterval(timer); 47 | timer = setInterval(() => updateTime(), 1000); 48 | }); 49 | 50 | onUnmounted(() => { 51 | clearInterval(timer); 52 | }); 53 | 54 | return { month, day, hour, minute, second, week }; 55 | } 56 | -------------------------------------------------------------------------------- /frontend/src/hooks/web/usePermission.ts: -------------------------------------------------------------------------------- 1 | import { useUserStore } from '@/store/modules/user'; 2 | 3 | export function usePermission() { 4 | const userStore = useUserStore(); 5 | 6 | /** 7 | * 检查权限 8 | * @param accesses 9 | */ 10 | function _somePermissions(accesses: string[]) { 11 | return userStore.getPermissions.some((item) => { 12 | const { value }: any = item; 13 | return accesses.includes(value); 14 | }); 15 | } 16 | 17 | /** 18 | * 判断是否存在权限 19 | * 可用于 v-if 显示逻辑 20 | * */ 21 | function hasPermission(accesses: string[]): boolean { 22 | if (!accesses || !accesses.length) return true; 23 | return _somePermissions(accesses); 24 | } 25 | 26 | /** 27 | * 是否包含指定的所有权限 28 | * @param accesses 29 | */ 30 | function hasEveryPermission(accesses: string[]): boolean { 31 | const permissionsList = userStore.getPermissions; 32 | if (Array.isArray(accesses)) { 33 | return permissionsList.every((access: any) => accesses.includes(access.value)); 34 | } 35 | throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`); 36 | } 37 | 38 | /** 39 | * 是否包含其中某个权限 40 | * @param accesses 41 | * @param accessMap 42 | */ 43 | function hasSomePermission(accesses: string[]): boolean { 44 | const permissionsList = userStore.getPermissions; 45 | if (Array.isArray(accesses)) { 46 | return permissionsList.some((access: any) => accesses.includes(access.value)); 47 | } 48 | throw new Error(`[hasSomePermission]: ${accesses} should be a array !`); 49 | } 50 | 51 | return { hasPermission, hasEveryPermission, hasSomePermission }; 52 | } 53 | -------------------------------------------------------------------------------- /frontend/src/layout/components/Footer/index.ts: -------------------------------------------------------------------------------- 1 | import PageFooter from './index.vue'; 2 | 3 | export { PageFooter }; 4 | -------------------------------------------------------------------------------- /frontend/src/layout/components/Footer/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | 24 | 57 | -------------------------------------------------------------------------------- /frontend/src/layout/components/Header/components.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SettingOutlined, 3 | SearchOutlined, 4 | MenuFoldOutlined, 5 | MenuUnfoldOutlined, 6 | FullscreenOutlined, 7 | FullscreenExitOutlined, 8 | PoweroffOutlined, 9 | GithubOutlined, 10 | LockOutlined, 11 | ReloadOutlined, 12 | LogoutOutlined, 13 | UserOutlined, 14 | CheckOutlined, 15 | } from '@vicons/antd'; 16 | 17 | export default { 18 | SettingOutlined, 19 | LockOutlined, 20 | GithubOutlined, 21 | SearchOutlined, 22 | MenuFoldOutlined, 23 | MenuUnfoldOutlined, 24 | FullscreenOutlined, 25 | FullscreenExitOutlined, 26 | PoweroffOutlined, 27 | ReloadOutlined, 28 | LogoutOutlined, 29 | UserOutlined, 30 | CheckOutlined, 31 | }; 32 | -------------------------------------------------------------------------------- /frontend/src/layout/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | import PageHeader from './index.vue'; 2 | 3 | export { PageHeader }; 4 | -------------------------------------------------------------------------------- /frontend/src/layout/components/Logo/index.ts: -------------------------------------------------------------------------------- 1 | import Logo from './index.vue'; 2 | 3 | export { Logo }; 4 | -------------------------------------------------------------------------------- /frontend/src/layout/components/Logo/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 45 | -------------------------------------------------------------------------------- /frontend/src/layout/components/Main/index.ts: -------------------------------------------------------------------------------- 1 | import MainView from './index.vue'; 2 | 3 | export { MainView }; 4 | -------------------------------------------------------------------------------- /frontend/src/layout/components/Main/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /frontend/src/layout/components/Menu/index.ts: -------------------------------------------------------------------------------- 1 | import AsideMenu from './index.vue'; 2 | 3 | export { AsideMenu }; 4 | -------------------------------------------------------------------------------- /frontend/src/layout/components/TagsView/index.ts: -------------------------------------------------------------------------------- 1 | import TabsView from './index.vue'; 2 | 3 | export { TabsView }; 4 | -------------------------------------------------------------------------------- /frontend/src/layout/parentLayout.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import './styles/tailwind.css'; 2 | import { createApp } from 'vue'; 3 | import App from './App.vue'; 4 | import router, { setupRouter } from './router'; 5 | import { setupStore } from '@/store'; 6 | import { setupNaive, setupDirectives } from '@/plugins'; 7 | import { AppProvider } from '@/components/Application'; 8 | import { VueClipboard } from '@soerenmartius/vue3-clipboard'; 9 | 10 | 11 | async function bootstrap() { 12 | const appProvider = createApp(AppProvider); 13 | 14 | const app = createApp(App); 15 | 16 | app.use(VueClipboard); 17 | // 注册全局常用的 naive-ui 组件 18 | setupNaive(app); 19 | 20 | // 注册全局自定义组件 21 | //setupCustomComponents(); 22 | 23 | // 注册全局自定义指令,如:v-permission权限指令 24 | setupDirectives(app); 25 | 26 | // 注册全局方法,如:app.config.globalProperties.$message = message 27 | //setupGlobalMethods(app); 28 | 29 | // 挂载状态管理 30 | setupStore(app); 31 | 32 | //优先挂载一下 Provider 解决路由守卫,Axios中可使用,Dialog,Message 等之类组件 33 | appProvider.mount('#appProvider', true); 34 | 35 | // 挂载路由 36 | await setupRouter(app); 37 | 38 | // 路由准备就绪后挂载APP实例 39 | await router.isReady(); 40 | 41 | 42 | app.mount('#app', true); 43 | } 44 | 45 | void bootstrap(); 46 | -------------------------------------------------------------------------------- /frontend/src/plugins/customComponents.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局注册自定义组件 待完善 3 | * @param app 4 | */ 5 | export function setupCustomComponents() { 6 | // app.component() 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/plugins/directives.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | 3 | import { permission } from '@/directives/permission'; 4 | 5 | /** 6 | * 注册全局自定义指令 7 | * @param app 8 | */ 9 | export function setupDirectives(app: App) { 10 | // 权限控制指令(演示) 11 | app.directive('permission', permission); 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/plugins/globalMethods.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 注册全局方法 待完善 3 | * @param app 4 | */ 5 | export function setupGlobalMethods() {} 6 | -------------------------------------------------------------------------------- /frontend/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export { setupNaive } from '@/plugins/naive'; 2 | export { setupDirectives } from '@/plugins/directives'; 3 | export { setupCustomComponents } from '@/plugins/customComponents'; 4 | export { setupGlobalMethods } from '@/plugins/globalMethods'; 5 | -------------------------------------------------------------------------------- /frontend/src/router/base.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteRecordRaw } from '@/router/types'; 2 | import { ErrorPage, RedirectName, Layout } from '@/router/constant'; 3 | 4 | // 404 on a page 5 | export const ErrorPageRoute: AppRouteRecordRaw = { 6 | path: '/:path(.*)*', 7 | name: 'ErrorPage', 8 | component: Layout, 9 | meta: { 10 | title: 'ErrorPage', 11 | hideBreadcrumb: true, 12 | }, 13 | children: [ 14 | { 15 | path: '/:path(.*)*', 16 | name: 'ErrorPageSon', 17 | component: ErrorPage, 18 | meta: { 19 | title: 'ErrorPage', 20 | hideBreadcrumb: true, 21 | }, 22 | }, 23 | ], 24 | }; 25 | 26 | export const RedirectRoute: AppRouteRecordRaw = { 27 | path: '/redirect', 28 | name: RedirectName, 29 | component: Layout, 30 | meta: { 31 | title: RedirectName, 32 | hideBreadcrumb: true, 33 | }, 34 | children: [ 35 | { 36 | path: '/redirect/:path(.*)', 37 | name: RedirectName, 38 | component: () => import('@/views/redirect/index.vue'), 39 | meta: { 40 | title: RedirectName, 41 | hideBreadcrumb: true, 42 | }, 43 | }, 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /frontend/src/router/constant.ts: -------------------------------------------------------------------------------- 1 | export const RedirectName = 'Redirect'; 2 | 3 | export const ErrorPage = () => import('@/views/exception/404.vue'); 4 | 5 | export const Layout = () => import('@/layout/index.vue'); 6 | 7 | export const ParentLayout = () => import('@/layout/parentLayout.vue'); 8 | -------------------------------------------------------------------------------- /frontend/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; 3 | import { RedirectRoute } from '@/router/base'; 4 | import { PageEnum } from '@/enums/pageEnum'; 5 | import { createRouterGuards } from './router-guards'; 6 | 7 | const modules = import.meta.globEager('./modules/**/*.ts'); 8 | 9 | const routeModuleList: RouteRecordRaw[] = []; 10 | 11 | Object.keys(modules).forEach((key) => { 12 | const mod = modules[key].default || {}; 13 | const modList = Array.isArray(mod) ? [...mod] : [mod]; 14 | routeModuleList.push(...modList); 15 | }); 16 | 17 | function sortRoute(a, b) { 18 | return (a.meta?.sort || 0) - (b.meta?.sort || 0); 19 | } 20 | 21 | routeModuleList.sort(sortRoute); 22 | 23 | export const RootRoute: RouteRecordRaw = { 24 | path: '/', 25 | name: 'Root', 26 | redirect: PageEnum.BASE_HOME, 27 | meta: { 28 | title: 'Root', 29 | }, 30 | }; 31 | 32 | export const LoginRoute: RouteRecordRaw = { 33 | path: '/login', 34 | name: 'Login', 35 | component: () => import('@/views/login/index.vue'), 36 | meta: { 37 | title: '登录', 38 | }, 39 | }; 40 | 41 | //需要验证权限 42 | export const asyncRoutes = [...routeModuleList]; 43 | 44 | //普通路由 无需验证权限 45 | export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute]; 46 | 47 | const router = createRouter({ 48 | history: createWebHashHistory(''), 49 | routes: constantRouter, 50 | strict: true, 51 | scrollBehavior: () => ({ left: 0, top: 0 }), 52 | }); 53 | 54 | export function setupRouter(app: App) { 55 | app.use(router); 56 | // 创建路由守卫 57 | createRouterGuards(router); 58 | } 59 | 60 | export default router; 61 | -------------------------------------------------------------------------------- /frontend/src/router/modules/about.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { ProjectOutlined } from '@vicons/antd'; 4 | import { renderIcon, renderNew } from '@/utils/index'; 5 | 6 | const routes: Array = [ 7 | { 8 | path: '/about', 9 | name: 'about', 10 | component: Layout, 11 | meta: { 12 | sort: 4, 13 | isRoot: true, 14 | activeMenu: 'about_index', 15 | icon: renderIcon(ProjectOutlined), 16 | }, 17 | children: [ 18 | { 19 | path: 'index', 20 | name: `about_index`, 21 | meta: { 22 | title: '关于', 23 | extra: renderNew(), 24 | activeMenu: 'about_index', 25 | }, 26 | component: () => import('@/views/about/index.vue'), 27 | }, 28 | ], 29 | }, 30 | ]; 31 | 32 | export default routes; 33 | -------------------------------------------------------------------------------- /frontend/src/router/modules/exception.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { ExclamationCircleOutlined } from '@vicons/antd'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | // { 19 | // path: '/exception', 20 | // name: 'Exception', 21 | // redirect: '/exception/403', 22 | // component: Layout, 23 | // meta: { 24 | // title: '异常页面', 25 | // icon: renderIcon(ExclamationCircleOutlined), 26 | // sort: 3, 27 | // }, 28 | // children: [ 29 | // { 30 | // path: '403', 31 | // name: 'exception-403', 32 | // meta: { 33 | // title: '403', 34 | // }, 35 | // component: () => import('@/views/exception/403.vue'), 36 | // }, 37 | // { 38 | // path: '404', 39 | // name: 'exception-404', 40 | // meta: { 41 | // title: '404', 42 | // }, 43 | // component: () => import('@/views/exception/404.vue'), 44 | // }, 45 | // { 46 | // path: '500', 47 | // name: 'exception-500', 48 | // meta: { 49 | // title: '500', 50 | // }, 51 | // component: () => import('@/views/exception/500.vue'), 52 | // }, 53 | // ], 54 | // }, 55 | ]; 56 | 57 | export default routes; 58 | -------------------------------------------------------------------------------- /frontend/src/router/modules/form.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { ProfileOutlined } from '@vicons/antd'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | // { 19 | // path: '/form', 20 | // name: 'Form', 21 | // redirect: '/form/basic-form', 22 | // component: Layout, 23 | // meta: { 24 | // title: '表单页面', 25 | // icon: renderIcon(ProfileOutlined), 26 | // sort: 3, 27 | // }, 28 | // children: [ 29 | // { 30 | // path: 'basic-form', 31 | // name: 'form-basic-form', 32 | // meta: { 33 | // title: '基础表单', 34 | // }, 35 | // component: () => import('@/views/form/basicForm/index.vue'), 36 | // }, 37 | // { 38 | // path: 'step-form', 39 | // name: 'form-step-form', 40 | // meta: { 41 | // title: '分步表单', 42 | // }, 43 | // component: () => import('@/views/form/stepForm/stepForm.vue'), 44 | // }, 45 | // { 46 | // path: 'detail', 47 | // name: 'form-detail', 48 | // meta: { 49 | // title: '表单详情', 50 | // }, 51 | // component: () => import('@/views/form/detail/index.vue'), 52 | // }, 53 | // ], 54 | // }, 55 | ]; 56 | 57 | export default routes; 58 | -------------------------------------------------------------------------------- /frontend/src/router/modules/result.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { CheckCircleOutlined } from '@vicons/antd'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | // { 19 | // path: '/result', 20 | // name: 'Result', 21 | // redirect: '/result/success', 22 | // component: Layout, 23 | // meta: { 24 | // title: '结果页面', 25 | // icon: renderIcon(CheckCircleOutlined), 26 | // sort: 4, 27 | // }, 28 | // children: [ 29 | // { 30 | // path: 'success', 31 | // name: 'result-success', 32 | // meta: { 33 | // title: '成功页', 34 | // }, 35 | // component: () => import('@/views/result/success.vue'), 36 | // }, 37 | // { 38 | // path: 'fail', 39 | // name: 'result-fail', 40 | // meta: { 41 | // title: '失败页', 42 | // }, 43 | // component: () => import('@/views/result/fail.vue'), 44 | // }, 45 | // { 46 | // path: 'info', 47 | // name: 'result-info', 48 | // meta: { 49 | // title: '信息页', 50 | // }, 51 | // component: () => import('@/views/result/info.vue'), 52 | // }, 53 | // ], 54 | // }, 55 | ]; 56 | 57 | export default routes; 58 | -------------------------------------------------------------------------------- /frontend/src/router/modules/setting.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { SettingOutlined } from '@vicons/antd'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | // { 19 | // path: '/setting', 20 | // name: 'Setting', 21 | // redirect: '/setting/account', 22 | // component: Layout, 23 | // meta: { 24 | // title: '设置页面', 25 | // icon: renderIcon(SettingOutlined), 26 | // sort: 5, 27 | // }, 28 | // children: [ 29 | // { 30 | // path: 'account', 31 | // name: 'setting-account', 32 | // meta: { 33 | // title: '个人设置', 34 | // }, 35 | // component: () => import('@/views/setting/account/account.vue'), 36 | // }, 37 | // { 38 | // path: 'system', 39 | // name: 'setting-system', 40 | // meta: { 41 | // title: '系统设置', 42 | // }, 43 | // component: () => import('@/views/setting/system/system.vue'), 44 | // }, 45 | // ], 46 | // }, 47 | ]; 48 | 49 | export default routes; 50 | -------------------------------------------------------------------------------- /frontend/src/router/modules/stores.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { TableOutlined } from '@vicons/antd'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | { 19 | path: '/stores', 20 | name: 'Stores', 21 | redirect: '/stores/pcap-list', 22 | component: Layout, 23 | meta: { 24 | title: '威胁仓库', 25 | icon: renderIcon(TableOutlined), 26 | sort: 1, 27 | }, 28 | children: [ 29 | { 30 | path: 'pcap-list', 31 | name: 'pcap-list', 32 | meta: { 33 | title: '流量包列表', 34 | }, 35 | component: () => import('@/views/stores/pcapList/index.vue'), 36 | }, 37 | { 38 | path: 'payload-list', 39 | name: 'payload-list', 40 | meta: { 41 | title: 'payload列表', 42 | }, 43 | component: () => import('@/views/stores/payloadList/index.vue'), 44 | }, 45 | { 46 | path: 'webshell-list', 47 | name: 'webshell-list', 48 | meta: { 49 | title: 'webshell列表', 50 | }, 51 | component: () => import('@/views/stores/webshellList/index.vue'), 52 | }, 53 | ], 54 | }, 55 | ]; 56 | 57 | export default routes; 58 | -------------------------------------------------------------------------------- /frontend/src/router/modules/system.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import { Layout } from '@/router/constant'; 3 | import { OptionsSharp } from '@vicons/ionicons5'; 4 | import { renderIcon } from '@/utils/index'; 5 | 6 | /** 7 | * @param name 路由名称, 必须设置,且不能重名 8 | * @param meta 路由元信息(路由附带扩展信息) 9 | * @param redirect 重定向地址, 访问这个路由时,自定进行重定向 10 | * @param meta.disabled 禁用整个菜单 11 | * @param meta.title 菜单名称 12 | * @param meta.icon 菜单图标 13 | * @param meta.keepAlive 缓存该路由 14 | * @param meta.sort 排序越小越排前 15 | * 16 | * */ 17 | const routes: Array = [ 18 | // { 19 | // path: '/system', 20 | // name: 'System', 21 | // redirect: '/system/menu', 22 | // component: Layout, 23 | // meta: { 24 | // title: '系统设置', 25 | // icon: renderIcon(OptionsSharp), 26 | // sort: 10, 27 | // }, 28 | // children: [ 29 | // { 30 | // path: 'menu', 31 | // name: 'system_menu', 32 | // meta: { 33 | // title: '菜单权限管理', 34 | // }, 35 | // component: () => import('@/views/system/menu/menu.vue'), 36 | // }, 37 | // { 38 | // path: 'role', 39 | // name: 'system_role', 40 | // meta: { 41 | // title: '角色权限管理', 42 | // }, 43 | // component: () => import('@/views/system/role/role.vue'), 44 | // }, 45 | // ], 46 | // }, 47 | ]; 48 | 49 | export default routes; 50 | -------------------------------------------------------------------------------- /frontend/src/router/router-icons.ts: -------------------------------------------------------------------------------- 1 | import { renderIcon } from '@/utils/index'; 2 | import { DashboardOutlined } from '@vicons/antd'; 3 | 4 | //前端路由图标映射表 5 | export const constantRouterIcon = { 6 | DashboardOutlined: renderIcon(DashboardOutlined), 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/router/types.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw, RouteMeta } from 'vue-router'; 2 | import { defineComponent } from 'vue'; 3 | 4 | export type Component = 5 | | ReturnType 6 | | (() => Promise) 7 | | (() => Promise); 8 | 9 | export interface AppRouteRecordRaw extends Omit { 10 | name: string; 11 | meta: RouteMeta; 12 | component?: Component | string; 13 | components?: Component; 14 | children?: AppRouteRecordRaw[]; 15 | props?: Recordable; 16 | fullPath?: string; 17 | } 18 | 19 | export interface Meta { 20 | // 名称 21 | title: string; 22 | // 是否忽略权限 23 | ignoreAuth?: boolean; 24 | permissions?: string[]; 25 | // 是否不缓存 26 | noKeepAlive?: boolean; 27 | // 是否固定在tab上 28 | affix?: boolean; 29 | // tab上的图标 30 | icon?: string; 31 | // 跳转地址 32 | frameSrc?: string; 33 | // 外链跳转地址 34 | externalLink?: string; 35 | //隐藏 36 | hidden?: boolean; 37 | } 38 | 39 | export interface Menu { 40 | title: string; 41 | label: string; 42 | key: string; 43 | meta: RouteMeta; 44 | name: string; 45 | component?: Component | string; 46 | components?: Component; 47 | children?: AppRouteRecordRaw[]; 48 | props?: Recordable; 49 | fullPath?: string; 50 | icon?: any; 51 | path: string; 52 | permissions?: string[]; 53 | redirect?: string; 54 | sort?: number; 55 | } 56 | -------------------------------------------------------------------------------- /frontend/src/settings/animateSetting.ts: -------------------------------------------------------------------------------- 1 | export const animates = [ 2 | { value: 'zoom-fade', label: '渐变' }, 3 | { value: 'zoom-out', label: '闪现' }, 4 | { value: 'fade-slide', label: '滑动' }, 5 | { value: 'fade', label: '消退' }, 6 | { value: 'fade-bottom', label: '底部消退' }, 7 | { value: 'fade-scale', label: '缩放消退' }, 8 | ]; 9 | -------------------------------------------------------------------------------- /frontend/src/settings/componentSetting.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | table: { 3 | apiSetting: { 4 | // 当前页的字段名 5 | pageField: 'page', 6 | // 每页数量字段名 7 | sizeField: 'pageSize', 8 | // 接口返回的数据字段名 9 | listField: 'list', 10 | // 接口返回总页数字段名 11 | totalField: 'pageCount', 12 | }, 13 | //默认分页数量 14 | defaultPageSize: 10, 15 | //可切换每页数量集合 16 | pageSizes: [10, 20, 30, 40, 50], 17 | }, 18 | upload: { 19 | //考虑接口规范不同 20 | apiSetting: { 21 | // 集合字段名 22 | infoField: 'data', 23 | // 图片地址字段名 24 | imgField: 'photo', 25 | }, 26 | //最大上传图片大小 27 | maxSize: 2, 28 | //图片上传类型 29 | fileType: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif', 'image/svg+xml'], 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /frontend/src/settings/designSetting.ts: -------------------------------------------------------------------------------- 1 | // app theme preset color 2 | export const appThemeList: string[] = [ 3 | '#2d8cf0', 4 | '#0960bd', 5 | '#0084f4', 6 | '#009688', 7 | '#536dfe', 8 | '#ff5c93', 9 | '#ee4f12', 10 | '#0096c7', 11 | '#9c27b0', 12 | '#ff9800', 13 | '#FF3D68', 14 | '#00C1D4', 15 | '#71EFA3', 16 | '#171010', 17 | '#78DEC7', 18 | '#1768AC', 19 | '#FB9300', 20 | '#FC5404', 21 | ]; 22 | 23 | const setting = { 24 | //深色主题 25 | darkTheme: false, 26 | //系统主题色 27 | appTheme: '#2d8cf0', 28 | //系统内置主题色列表 29 | appThemeList, 30 | }; 31 | 32 | export default setting; 33 | -------------------------------------------------------------------------------- /frontend/src/settings/projectSetting.ts: -------------------------------------------------------------------------------- 1 | const setting = { 2 | //导航模式 vertical 左侧菜单模式 horizontal 顶部菜单模式 3 | navMode: 'vertical', 4 | //导航风格 dark 暗色侧边栏 light 白色侧边栏 header-dark 暗色顶栏 5 | navTheme: 'dark', 6 | // 是否处于移动端模式 7 | isMobile: false, 8 | //顶部 9 | headerSetting: { 10 | //背景色 11 | bgColor: '#fff', 12 | //固定顶部 13 | fixed: true, 14 | //显示重载按钮 15 | isReload: true, 16 | }, 17 | //页脚 18 | showFooter: true, 19 | //多标签 20 | multiTabsSetting: { 21 | //背景色 22 | bgColor: '#fff', 23 | //是否显示 24 | show: true, 25 | //固定多标签 26 | fixed: true, 27 | }, 28 | //菜单 29 | menuSetting: { 30 | //最小宽度 31 | minMenuWidth: 64, 32 | //菜单宽度 33 | menuWidth: 200, 34 | //固定菜单 35 | fixed: true, 36 | //分割菜单 37 | mixMenu: false, 38 | //触发移动端侧边栏的宽度 39 | mobileWidth: 800, 40 | // 折叠菜单 41 | collapsed: false, 42 | }, 43 | //面包屑 44 | crumbsSetting: { 45 | //是否显示 46 | show: true, 47 | //显示图标 48 | showIcon: false, 49 | }, 50 | //菜单权限模式 FIXED 前端固定路由 BACK 动态获取 51 | permissionMode: 'FIXED', 52 | //是否开启路由动画 53 | isPageAnimate: true, 54 | //路由动画类型 55 | pageAnimateType: 'zoom-fade', 56 | }; 57 | export default setting; 58 | -------------------------------------------------------------------------------- /frontend/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue'; 2 | import { createPinia } from 'pinia'; 3 | 4 | const store = createPinia(); 5 | 6 | export function setupStore(app: App) { 7 | app.use(store); 8 | } 9 | 10 | export { store }; 11 | -------------------------------------------------------------------------------- /frontend/src/store/modules/designSetting.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { store } from '@/store'; 3 | import designSetting from '@/settings/designSetting'; 4 | 5 | const { darkTheme, appTheme, appThemeList } = designSetting; 6 | 7 | interface DesignSettingState { 8 | //深色主题 9 | darkTheme: boolean; 10 | //系统风格 11 | appTheme: string; 12 | //系统内置风格 13 | appThemeList: string[]; 14 | } 15 | 16 | export const useDesignSettingStore = defineStore({ 17 | id: 'app-design-setting', 18 | state: (): DesignSettingState => ({ 19 | darkTheme, 20 | appTheme, 21 | appThemeList, 22 | }), 23 | getters: { 24 | getDarkTheme(): boolean { 25 | return this.darkTheme; 26 | }, 27 | getAppTheme(): string { 28 | return this.appTheme; 29 | }, 30 | getAppThemeList(): string[] { 31 | return this.appThemeList; 32 | }, 33 | }, 34 | actions: {}, 35 | }); 36 | 37 | // Need to be used outside the setup 38 | export function useDesignSettingWithOut() { 39 | return useDesignSettingStore(store); 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/store/modules/index.ts: -------------------------------------------------------------------------------- 1 | const allModules = import.meta.globEager('./*/index.ts'); 2 | const modules = {} as any; 3 | Object.keys(allModules).forEach((path) => { 4 | const fileName = path.split('/')[1]; 5 | modules[fileName] = allModules[path][fileName] || allModules[path].default || allModules[path]; 6 | }); 7 | 8 | // export default modules 9 | import asyncRoute from './async-route'; 10 | import user from './user'; 11 | import tabsView from './tabs-view'; 12 | import lockscreen from './lockscreen'; 13 | 14 | export default { 15 | asyncRoute, 16 | user, 17 | tabsView, 18 | lockscreen, 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/store/modules/lockscreen.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { IS_LOCKSCREEN } from '@/store/mutation-types'; 3 | import { storage } from '@/utils/Storage'; 4 | 5 | // 长时间不操作默认锁屏时间 6 | const initTime = 60 * 60; 7 | 8 | const isLock = storage.get(IS_LOCKSCREEN, false); 9 | 10 | export type ILockscreenState = { 11 | isLock: boolean; // 是否锁屏 12 | lockTime: number; 13 | }; 14 | 15 | export const useLockscreenStore = defineStore({ 16 | id: 'app-lockscreen', 17 | state: (): ILockscreenState => ({ 18 | isLock: isLock === true, // 是否锁屏 19 | lockTime: isLock == 'true' ? initTime : 0, 20 | }), 21 | getters: {}, 22 | actions: { 23 | setLock(payload) { 24 | this.isLock = payload; 25 | storage.set(IS_LOCKSCREEN, this.isLock); 26 | }, 27 | setLockTime(payload = initTime) { 28 | this.lockTime = payload; 29 | }, 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /frontend/src/store/mutation-types.ts: -------------------------------------------------------------------------------- 1 | export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token 2 | export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息 3 | export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏 4 | export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页 5 | -------------------------------------------------------------------------------- /frontend/src/store/types.ts: -------------------------------------------------------------------------------- 1 | import { IAsyncRouteState } from '@/store/modules/asyncRoute'; 2 | import { IUserState } from '@/store/modules/user'; 3 | import { ILockscreenState } from '@/store/modules/lockscreen'; 4 | import { ITabsViewState } from '@/store/modules/tabsView'; 5 | 6 | export interface IStore { 7 | asyncRoute: IAsyncRouteState; 8 | user: IUserState; 9 | lockscreen: ILockscreenState; 10 | tabsView: ITabsViewState; 11 | count: number; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import 'transition/index.less'; 2 | @import './var.less'; 3 | @import './common.less'; 4 | -------------------------------------------------------------------------------- /frontend/src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | /*! @import */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | -------------------------------------------------------------------------------- /frontend/src/styles/transition/base.less: -------------------------------------------------------------------------------- 1 | .transition-default() { 2 | &-enter-active, 3 | &-leave-active { 4 | transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important; 5 | } 6 | 7 | &-move { 8 | transition: transform 0.4s; 9 | } 10 | } 11 | 12 | .expand-transition { 13 | .transition-default(); 14 | } 15 | 16 | .expand-x-transition { 17 | .transition-default(); 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/styles/transition/fade.less: -------------------------------------------------------------------------------- 1 | .fade-enter-active, 2 | .fade-leave-active { 3 | transition: opacity 0.2s ease-in-out; 4 | } 5 | 6 | .fade-enter-from, 7 | .fade-leave-to { 8 | opacity: 0; 9 | } 10 | 11 | /* fade-slide */ 12 | .fade-slide-leave-active, 13 | .fade-slide-enter-active { 14 | transition: all 0.3s; 15 | } 16 | 17 | .fade-slide-enter-from { 18 | opacity: 0; 19 | transform: translateX(-30px); 20 | } 21 | 22 | .fade-slide-leave-to { 23 | opacity: 0; 24 | transform: translateX(30px); 25 | } 26 | 27 | // /////////////////////////////////////////////// 28 | // Fade Bottom 29 | // /////////////////////////////////////////////// 30 | 31 | // Speed: 1x 32 | .fade-bottom-enter-active, 33 | .fade-bottom-leave-active { 34 | transition: opacity 0.25s, transform 0.3s; 35 | } 36 | 37 | .fade-bottom-enter-from { 38 | opacity: 0; 39 | transform: translateY(-10%); 40 | } 41 | 42 | .fade-bottom-leave-to { 43 | opacity: 0; 44 | transform: translateY(10%); 45 | } 46 | 47 | // fade-scale 48 | .fade-scale-leave-active, 49 | .fade-scale-enter-active { 50 | transition: all 0.28s; 51 | } 52 | 53 | .fade-scale-enter-from { 54 | opacity: 0; 55 | transform: scale(1.2); 56 | } 57 | 58 | .fade-scale-leave-to { 59 | opacity: 0; 60 | transform: scale(0.8); 61 | } 62 | 63 | // /////////////////////////////////////////////// 64 | // Fade Top 65 | // /////////////////////////////////////////////// 66 | 67 | // Speed: 1x 68 | .fade-top-enter-active, 69 | .fade-top-leave-active { 70 | transition: opacity 0.2s, transform 0.25s; 71 | } 72 | 73 | .fade-top-enter-from { 74 | opacity: 0; 75 | transform: translateY(8%); 76 | } 77 | 78 | .fade-top-leave-to { 79 | opacity: 0; 80 | transform: translateY(-8%); 81 | } 82 | -------------------------------------------------------------------------------- /frontend/src/styles/transition/index.less: -------------------------------------------------------------------------------- 1 | @import './base.less'; 2 | @import './fade.less'; 3 | @import './scale.less'; 4 | @import './slide.less'; 5 | @import './scroll.less'; 6 | @import './zoom.less'; 7 | 8 | .collapse-transition { 9 | transition: 0.2s height ease-in-out, 0.2s padding-top ease-in-out, 0.2s padding-bottom ease-in-out; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/styles/transition/scale.less: -------------------------------------------------------------------------------- 1 | .scale-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave, 6 | &-leave-to { 7 | opacity: 0; 8 | transform: scale(0); 9 | } 10 | } 11 | 12 | .scale-rotate-transition { 13 | .transition-default(); 14 | 15 | &-enter-from, 16 | &-leave, 17 | &-leave-to { 18 | opacity: 0; 19 | transform: scale(0) rotate(-45deg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/styles/transition/scroll.less: -------------------------------------------------------------------------------- 1 | .scroll-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | } 8 | 9 | &-enter-from { 10 | transform: translateY(-15px); 11 | } 12 | 13 | &-leave-to { 14 | transform: translateY(15px); 15 | } 16 | } 17 | 18 | .scroll-y-reverse-transition { 19 | .transition-default(); 20 | 21 | &-enter-from, 22 | &-leave-to { 23 | opacity: 0; 24 | } 25 | 26 | &-enter-from { 27 | transform: translateY(15px); 28 | } 29 | 30 | &-leave-to { 31 | transform: translateY(-15px); 32 | } 33 | } 34 | 35 | .scroll-x-transition { 36 | .transition-default(); 37 | 38 | &-enter-from, 39 | &-leave-to { 40 | opacity: 0; 41 | } 42 | 43 | &-enter-from { 44 | transform: translateX(-15px); 45 | } 46 | 47 | &-leave-to { 48 | transform: translateX(15px); 49 | } 50 | } 51 | 52 | .scroll-x-reverse-transition { 53 | .transition-default(); 54 | 55 | &-enter-from, 56 | &-leave-to { 57 | opacity: 0; 58 | } 59 | 60 | &-enter-from { 61 | transform: translateX(15px); 62 | } 63 | 64 | &-leave-to { 65 | transform: translateX(-15px); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /frontend/src/styles/transition/slide.less: -------------------------------------------------------------------------------- 1 | .slide-y-transition { 2 | .transition-default(); 3 | 4 | &-enter-from, 5 | &-leave-to { 6 | opacity: 0; 7 | transform: translateY(-15px); 8 | } 9 | } 10 | 11 | .slide-y-reverse-transition { 12 | .transition-default(); 13 | 14 | &-enter-from, 15 | &-leave-to { 16 | opacity: 0; 17 | transform: translateY(15px); 18 | } 19 | } 20 | 21 | .slide-x-transition { 22 | .transition-default(); 23 | 24 | &-enter-from, 25 | &-leave-to { 26 | opacity: 0; 27 | transform: translateX(-15px); 28 | } 29 | } 30 | 31 | .slide-x-reverse-transition { 32 | .transition-default(); 33 | 34 | &-enter-from, 35 | &-leave-to { 36 | opacity: 0; 37 | transform: translateX(15px); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/styles/transition/zoom.less: -------------------------------------------------------------------------------- 1 | // zoom-out 2 | .zoom-out-enter-active, 3 | .zoom-out-leave-active { 4 | transition: opacity 0.1 ease-in-out, transform 0.15s ease-out; 5 | } 6 | 7 | .zoom-out-enter-from, 8 | .zoom-out-leave-to { 9 | opacity: 0; 10 | transform: scale(0); 11 | } 12 | 13 | // zoom-fade 14 | .zoom-fade-enter-active, 15 | .zoom-fade-leave-active { 16 | transition: transform 0.2s, opacity 0.3s ease-out; 17 | } 18 | 19 | .zoom-fade-enter-from { 20 | opacity: 0; 21 | transform: scale(0.92); 22 | } 23 | 24 | .zoom-fade-leave-to { 25 | opacity: 0; 26 | transform: scale(1.06); 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/styles/var.less: -------------------------------------------------------------------------------- 1 | @primaryColor: #2d8cf0; 2 | @primaryColorHover: #57a3f3; 3 | @header-height: 64px; 4 | @footer-height: 70px; 5 | -------------------------------------------------------------------------------- /frontend/src/utils/dateUtil.ts: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns'; 2 | 3 | const DATE_TIME_FORMAT = 'yyyy-MM-dd hh:mm'; 4 | const DATE_FORMAT = 'YYYY-MM-dd '; 5 | const DATE_TIME_FORMAT2 = 'yyyy-MM-dd hh:mm:ss'; 6 | 7 | export function formatToDateTime(date, formatStr = DATE_TIME_FORMAT): string { 8 | return format(date, formatStr); 9 | } 10 | 11 | export function formatToDate(date, formatStr = DATE_FORMAT): string { 12 | return format(date, formatStr); 13 | } 14 | 15 | 16 | 17 | function padLeftZero (str) { 18 | return ('00' + str).substr(str.length); 19 | }; 20 | 21 | 22 | export function formatDate (date, fmt = DATE_TIME_FORMAT2): string { 23 | if (/(y+)/.test(fmt)) { 24 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); 25 | } 26 | let o = { 27 | 'M+': date.getMonth() + 1, 28 | 'd+': date.getDate(), 29 | 'h+': date.getHours(), 30 | 'm+': date.getMinutes(), 31 | 's+': date.getSeconds() 32 | }; 33 | for (let k in o) { 34 | if (new RegExp(`(${k})`).test(fmt)) { 35 | let str = o[k] + ''; 36 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str)); 37 | } 38 | } 39 | return fmt; 40 | }; 41 | 42 | -------------------------------------------------------------------------------- /frontend/src/utils/http/axios/axiosCancel.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig, Canceler } from 'axios'; 2 | import qs from 'qs'; 3 | 4 | import { isFunction } from '@/utils/is/index'; 5 | 6 | // 声明一个 Map 用于存储每个请求的标识 和 取消函数 7 | let pendingMap = new Map(); 8 | 9 | export const getPendingUrl = (config: AxiosRequestConfig) => 10 | [config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&'); 11 | 12 | export class AxiosCanceler { 13 | /** 14 | * 添加请求 15 | * @param {Object} config 16 | */ 17 | addPending(config: AxiosRequestConfig) { 18 | this.removePending(config); 19 | const url = getPendingUrl(config); 20 | config.cancelToken = 21 | config.cancelToken || 22 | new axios.CancelToken((cancel) => { 23 | if (!pendingMap.has(url)) { 24 | // 如果 pending 中不存在当前请求,则添加进去 25 | pendingMap.set(url, cancel); 26 | } 27 | }); 28 | } 29 | 30 | /** 31 | * @description: 清空所有pending 32 | */ 33 | removeAllPending() { 34 | pendingMap.forEach((cancel) => { 35 | cancel && isFunction(cancel) && cancel(); 36 | }); 37 | pendingMap.clear(); 38 | } 39 | 40 | /** 41 | * 移除请求 42 | * @param {Object} config 43 | */ 44 | removePending(config: AxiosRequestConfig) { 45 | const url = getPendingUrl(config); 46 | 47 | if (pendingMap.has(url)) { 48 | // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除 49 | const cancel = pendingMap.get(url); 50 | cancel && cancel(url); 51 | pendingMap.delete(url); 52 | } 53 | } 54 | 55 | /** 56 | * @description: 重置 57 | */ 58 | reset(): void { 59 | pendingMap = new Map(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /frontend/src/utils/http/axios/axiosTransform.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 数据处理类,可以根据项目自行配置 3 | */ 4 | import type { AxiosRequestConfig, AxiosResponse } from 'axios'; 5 | import type { RequestOptions, Result } from './types'; 6 | 7 | export interface CreateAxiosOptions extends AxiosRequestConfig { 8 | authenticationScheme?: string; 9 | transform?: AxiosTransform; 10 | requestOptions?: RequestOptions; 11 | } 12 | 13 | export abstract class AxiosTransform { 14 | /** 15 | * @description: 请求之前处理配置 16 | * @description: Process configuration before request 17 | */ 18 | beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig; 19 | 20 | /** 21 | * @description: 请求成功处理 22 | */ 23 | transformRequestData?: (res: AxiosResponse, options: RequestOptions) => any; 24 | 25 | /** 26 | * @description: 请求失败处理 27 | */ 28 | requestCatch?: (e: Error) => Promise; 29 | 30 | /** 31 | * @description: 请求之前的拦截器 32 | */ 33 | requestInterceptors?: ( 34 | config: AxiosRequestConfig, 35 | options: CreateAxiosOptions 36 | ) => AxiosRequestConfig; 37 | 38 | /** 39 | * @description: 请求之后的拦截器 40 | */ 41 | responseInterceptors?: (res: AxiosResponse) => AxiosResponse; 42 | 43 | /** 44 | * @description: 请求之前的拦截器错误处理 45 | */ 46 | requestInterceptorsCatch?: (error: Error) => void; 47 | 48 | /** 49 | * @description: 请求之后的拦截器错误处理 50 | */ 51 | responseInterceptorsCatch?: (error: Error) => void; 52 | } 53 | -------------------------------------------------------------------------------- /frontend/src/utils/http/axios/checkStatus.ts: -------------------------------------------------------------------------------- 1 | export function checkStatus(status: number, msg: string): void { 2 | const $message = window['$message']; 3 | switch (status) { 4 | case 400: 5 | $message.error(msg); 6 | break; 7 | // 401: 未登录 8 | // 未登录则跳转登录页面,并携带当前页面的路径 9 | // 在登录成功后返回当前页面,这一步需要在登录页操作。 10 | case 401: 11 | $message.error('用户没有权限(令牌、用户名、密码错误)!'); 12 | break; 13 | case 403: 14 | $message.error('用户得到授权,但是访问是被禁止的。!'); 15 | break; 16 | // 404请求不存在 17 | case 404: 18 | $message.error('网络请求错误,未找到该资源!'); 19 | break; 20 | case 405: 21 | $message.error('网络请求错误,请求方法未允许!'); 22 | break; 23 | case 408: 24 | $message.error('网络请求超时'); 25 | break; 26 | case 500: 27 | $message.error('服务器错误,请联系管理员!'); 28 | break; 29 | case 501: 30 | $message.error('网络未实现'); 31 | break; 32 | case 502: 33 | $message.error('网络错误'); 34 | break; 35 | case 503: 36 | $message.error('服务不可用,服务器暂时过载或维护!'); 37 | break; 38 | case 504: 39 | $message.error('网络超时'); 40 | break; 41 | case 505: 42 | $message.error('http版本不支持该请求!'); 43 | break; 44 | default: 45 | $message.error(msg); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/src/utils/http/axios/helper.ts: -------------------------------------------------------------------------------- 1 | import { isObject, isString } from '@/utils/is'; 2 | 3 | const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; 4 | 5 | export function joinTimestamp( 6 | join: boolean, 7 | restful: T 8 | ): T extends true ? string : object; 9 | 10 | export function joinTimestamp(join: boolean, restful = false): string | object { 11 | if (!join) { 12 | return restful ? '' : {}; 13 | } 14 | const now = new Date().getTime(); 15 | if (restful) { 16 | return `?_t=${now}`; 17 | } 18 | return { _t: now }; 19 | } 20 | 21 | /** 22 | * @description: Format request parameter time 23 | */ 24 | export function formatRequestDate(params: Recordable) { 25 | if (Object.prototype.toString.call(params) !== '[object Object]') { 26 | return; 27 | } 28 | 29 | for (const key in params) { 30 | if (params[key] && params[key]._isAMomentObject) { 31 | params[key] = params[key].format(DATE_TIME_FORMAT); 32 | } 33 | if (isString(key)) { 34 | const value = params[key]; 35 | if (value) { 36 | try { 37 | params[key] = isString(value) ? value.trim() : value; 38 | } catch (error) { 39 | throw new Error(error as any); 40 | } 41 | } 42 | } 43 | if (isObject(params[key])) { 44 | formatRequestDate(params[key]); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/src/utils/http/axios/types.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig } from 'axios'; 2 | import { AxiosTransform } from './axiosTransform'; 3 | 4 | export interface CreateAxiosOptions extends AxiosRequestConfig { 5 | transform?: AxiosTransform; 6 | requestOptions?: RequestOptions; 7 | authenticationScheme?: string; 8 | } 9 | 10 | // 上传文件 11 | export interface UploadFileParams { 12 | // 其他参数 13 | data?: Recordable; 14 | // 文件参数接口字段名 15 | name?: string; 16 | // 文件 17 | file: File | Blob; 18 | // 文件名称 19 | filename?: string; 20 | [key: string]: any; 21 | } 22 | 23 | export interface RequestOptions { 24 | // 请求参数拼接到url 25 | joinParamsToUrl?: boolean; 26 | // 格式化请求参数时间 27 | formatDate?: boolean; 28 | // 是否显示提示信息 29 | isShowMessage?: boolean; 30 | // 是否解析成JSON 31 | isParseToJson?: boolean; 32 | // 成功的文本信息 33 | successMessageText?: string; 34 | // 是否显示成功信息 35 | isShowSuccessMessage?: boolean; 36 | // 是否显示失败信息 37 | isShowErrorMessage?: boolean; 38 | // 错误的文本信息 39 | errorMessageText?: string; 40 | // 是否加入url 41 | joinPrefix?: boolean; 42 | // 接口地址, 不填则使用默认apiUrl 43 | apiUrl?: string; 44 | // 请求拼接路径 45 | urlPrefix?: string; 46 | // 错误消息提示类型 47 | errorMessageMode?: 'none' | 'modal'; 48 | // 是否添加时间戳 49 | joinTime?: boolean; 50 | // 不进行任何处理,直接返回 51 | isTransformResponse?: boolean; 52 | // 是否返回原生响应头 53 | isReturnNativeResponse?: boolean; 54 | //忽略重复请求 55 | ignoreCancelToken?: boolean; 56 | // 是否携带token 57 | withToken?: boolean; 58 | } 59 | 60 | export interface Result { 61 | code: number; 62 | type?: 'success' | 'error' | 'warning'; 63 | message: string; 64 | result?: T; 65 | } 66 | -------------------------------------------------------------------------------- /frontend/src/utils/lodashChunk.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里按需引入lodash的一些方法,方便维护 3 | */ 4 | 5 | // export {default as xxx} from 'lodash/xxx' 6 | 7 | export { default as cloneDeep } from 'lodash/cloneDeep'; 8 | export { default as intersection } from 'lodash/intersection'; 9 | export { default as get } from 'lodash/get'; 10 | export { default as upperFirst } from 'lodash/upperFirst'; 11 | export { default as omit } from 'lodash/omit'; 12 | export { default as debounce } from 'lodash/debounce'; 13 | -------------------------------------------------------------------------------- /frontend/src/utils/log.ts: -------------------------------------------------------------------------------- 1 | const projectName = import.meta.env.VITE_GLOB_APP_TITLE; 2 | 3 | export function warn(message: string) { 4 | console.warn(`[${projectName} warn]:${message}`); 5 | } 6 | 7 | export function error(message: string) { 8 | throw new Error(`[${projectName} error]:${message}`); 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/utils/propTypes.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties, VNodeChild } from 'vue'; 2 | import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types'; 3 | 4 | export type VueNode = VNodeChild | JSX.Element; 5 | 6 | type PropTypes = VueTypesInterface & { 7 | readonly style: VueTypeValidableDef; 8 | readonly VNodeChild: VueTypeValidableDef; 9 | }; 10 | 11 | const propTypes = createTypes({ 12 | func: undefined, 13 | bool: undefined, 14 | string: undefined, 15 | number: undefined, 16 | object: undefined, 17 | integer: undefined, 18 | }) as PropTypes; 19 | 20 | propTypes.extend([ 21 | { 22 | name: 'style', 23 | getter: true, 24 | type: [String, Object], 25 | default: undefined, 26 | }, 27 | { 28 | name: 'VNodeChild', 29 | getter: true, 30 | type: undefined, 31 | }, 32 | ]); 33 | export { propTypes }; 34 | -------------------------------------------------------------------------------- /frontend/src/utils/urlUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 将对象添加当作参数拼接到URL上面 3 | * @param baseUrl 需要拼接的url 4 | * @param obj 参数对象 5 | * @returns {string} 拼接后的对象 6 | * 例子: 7 | * let obj = {a: '3', b: '4'} 8 | * setObjToUrlParams('www.baidu.com', obj) 9 | * ==>www.baidu.com?a=3&b=4 10 | */ 11 | export function setObjToUrlParams(baseUrl: string, obj: object): string { 12 | let parameters = ''; 13 | let url = ''; 14 | for (const key in obj) { 15 | parameters += key + '=' + encodeURIComponent(obj[key]) + '&'; 16 | } 17 | parameters = parameters.replace(/&$/, ''); 18 | if (/\?$/.test(baseUrl)) { 19 | url = baseUrl + parameters; 20 | } else { 21 | url = baseUrl.replace(/\/?$/, '?') + parameters; 22 | } 23 | return url; 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/views/comp/table/CellColumns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '编码', 12 | key: 'no', 13 | width: 100, 14 | }, 15 | { 16 | title: '名称', 17 | key: 'name', 18 | editComponent: 'NInput', 19 | // 默认必填校验 20 | editRule: true, 21 | edit: true, 22 | width: 200, 23 | }, 24 | { 25 | title: '头像', 26 | key: 'avatar', 27 | width: 100, 28 | render(row) { 29 | return h(NAvatar, { 30 | size: 48, 31 | src: row.avatar, 32 | }); 33 | }, 34 | }, 35 | { 36 | title: '地址', 37 | key: 'address', 38 | editComponent: 'NSelect', 39 | editComponentProps: { 40 | options: [ 41 | { 42 | label: '广东省', 43 | value: 1, 44 | }, 45 | { 46 | label: '浙江省', 47 | value: 2, 48 | }, 49 | ], 50 | }, 51 | edit: true, 52 | width: 200, 53 | ellipsis: false, 54 | }, 55 | { 56 | title: '开始日期', 57 | key: 'beginTime', 58 | edit: true, 59 | width: 160, 60 | editComponent: 'NDatePicker', 61 | editComponentProps: { 62 | type: 'datetime', 63 | format: 'yyyy-MM-dd HH:mm:ss', 64 | valueFormat: 'yyyy-MM-dd HH:mm:ss', 65 | }, 66 | ellipsis: false, 67 | }, 68 | { 69 | title: '结束日期', 70 | key: 'endTime', 71 | width: 160, 72 | }, 73 | { 74 | title: '创建时间', 75 | key: 'date', 76 | width: 160, 77 | }, 78 | { 79 | title: '停留时间', 80 | key: 'time', 81 | width: 80, 82 | }, 83 | ]; 84 | -------------------------------------------------------------------------------- /frontend/src/views/comp/table/basicColumns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar, NTag } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '编码', 12 | key: 'no', 13 | width: 100, 14 | }, 15 | { 16 | title: '名称', 17 | key: 'name', 18 | width: 100, 19 | }, 20 | { 21 | title: '头像', 22 | key: 'avatar', 23 | width: 100, 24 | render(row) { 25 | return h(NAvatar, { 26 | size: 48, 27 | src: row.avatar, 28 | }); 29 | }, 30 | }, 31 | { 32 | title: '地址', 33 | key: 'address', 34 | width: 150, 35 | }, 36 | { 37 | title: '开始日期', 38 | key: 'beginTime', 39 | width: 160, 40 | }, 41 | { 42 | title: '结束日期', 43 | key: 'endTime', 44 | width: 160, 45 | }, 46 | { 47 | title: '状态', 48 | key: 'status', 49 | width: 100, 50 | render(row) { 51 | return h( 52 | NTag, 53 | { 54 | type: row.status ? 'success' : 'error', 55 | }, 56 | { 57 | default: () => (row.status ? '启用' : '禁用'), 58 | } 59 | ); 60 | }, 61 | }, 62 | { 63 | title: '创建时间', 64 | key: 'date', 65 | width: 160, 66 | }, 67 | { 68 | title: '停留时间', 69 | key: 'time', 70 | width: 80, 71 | }, 72 | ]; 73 | -------------------------------------------------------------------------------- /frontend/src/views/comp/table/editCell.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/console/components/Icons.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CaretUpOutlined, 3 | CaretDownOutlined, 4 | UsergroupAddOutlined, 5 | BarChartOutlined, 6 | ShoppingCartOutlined, 7 | AccountBookOutlined, 8 | CreditCardOutlined, 9 | MailOutlined, 10 | TagsOutlined, 11 | SettingOutlined, 12 | } from '@vicons/antd'; 13 | 14 | export default { 15 | CaretUpOutlined, 16 | CaretDownOutlined, 17 | UsergroupAddOutlined, 18 | BarChartOutlined, 19 | ShoppingCartOutlined, 20 | AccountBookOutlined, 21 | CreditCardOutlined, 22 | MailOutlined, 23 | TagsOutlined, 24 | SettingOutlined, 25 | }; 26 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/console/components/VisiTab.vue: -------------------------------------------------------------------------------- 1 | 19 | 31 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/console/components/props.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from 'vue'; 2 | 3 | export interface BasicProps { 4 | width: string; 5 | height: string; 6 | } 7 | 8 | export const basicProps = { 9 | width: { 10 | type: String as PropType, 11 | default: '100%', 12 | }, 13 | height: { 14 | type: String as PropType, 15 | default: '280px', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/monitor/monitor.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/views/exception/403.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 41 | -------------------------------------------------------------------------------- /frontend/src/views/exception/404.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 41 | -------------------------------------------------------------------------------- /frontend/src/views/exception/500.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 41 | -------------------------------------------------------------------------------- /frontend/src/views/form/stepForm/stepForm.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 48 | 49 | 55 | -------------------------------------------------------------------------------- /frontend/src/views/frame/docs.vue: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /frontend/src/views/result/success.vue: -------------------------------------------------------------------------------- 1 | 25 | 41 | 56 | -------------------------------------------------------------------------------- /frontend/src/views/risk/basicList/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '名称', 12 | key: 'name', 13 | width: 100, 14 | }, 15 | { 16 | title: '头像', 17 | key: 'avatar', 18 | width: 100, 19 | render(row) { 20 | return h(NAvatar, { 21 | size: 48, 22 | src: row.avatar, 23 | }); 24 | }, 25 | }, 26 | { 27 | title: '地址', 28 | key: 'address', 29 | auth: ['basic_list'], // 同时根据权限控制是否显示 30 | ifShow: (_column) => { 31 | return true; // 根据业务控制是否显示 32 | }, 33 | width: 150, 34 | }, 35 | { 36 | title: '开始日期', 37 | key: 'beginTime', 38 | width: 160, 39 | }, 40 | { 41 | title: '结束日期', 42 | key: 'endTime', 43 | width: 160, 44 | }, 45 | { 46 | title: '创建时间', 47 | key: 'date', 48 | width: 100, 49 | }, 50 | ]; 51 | -------------------------------------------------------------------------------- /frontend/src/views/risk/basicList/info.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /frontend/src/views/risk/http_parse/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/frontend/src/views/risk/http_parse/.gitkeep -------------------------------------------------------------------------------- /frontend/src/views/stores/basicList/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NAvatar } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | width: 100, 9 | }, 10 | { 11 | title: '名称', 12 | key: 'name', 13 | width: 100, 14 | }, 15 | { 16 | title: '头像', 17 | key: 'avatar', 18 | width: 100, 19 | render(row) { 20 | return h(NAvatar, { 21 | size: 48, 22 | src: row.avatar, 23 | }); 24 | }, 25 | }, 26 | { 27 | title: '地址', 28 | key: 'address', 29 | auth: ['basic_list'], // 同时根据权限控制是否显示 30 | ifShow: (_column) => { 31 | return true; // 根据业务控制是否显示 32 | }, 33 | width: 150, 34 | }, 35 | { 36 | title: '开始日期', 37 | key: 'beginTime', 38 | width: 160, 39 | }, 40 | { 41 | title: '结束日期', 42 | key: 'endTime', 43 | width: 160, 44 | }, 45 | { 46 | title: '创建时间', 47 | key: 'date', 48 | width: 100, 49 | }, 50 | ]; 51 | -------------------------------------------------------------------------------- /frontend/src/views/stores/basicList/info.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /frontend/src/views/stores/payloadList/columns.ts: -------------------------------------------------------------------------------- 1 | 2 | export const columns = [ 3 | { 4 | title: 'id', 5 | key: 'id', 6 | width: 30, 7 | }, 8 | { 9 | title: '名称', 10 | key: 'name', 11 | width: 100, 12 | }, 13 | // { 14 | // title: '描述', 15 | // key: 'description', 16 | // width: 100, 17 | // }, 18 | { 19 | title: 'sha-1', 20 | key: 'sha_1', 21 | width: 150, 22 | }, 23 | { 24 | title: '大小', 25 | key: 'size', 26 | width: 100, 27 | render(row) { 28 | return (row.size / 1000).toFixed(1) + "KB"; 29 | } 30 | }, 31 | { 32 | title: '上传时间', 33 | key: 'CreatedAt', 34 | width: 150, 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /frontend/src/views/stores/pcapList/columns.ts: -------------------------------------------------------------------------------- 1 | 2 | export const columns = [ 3 | { 4 | title: 'id', 5 | key: 'id', 6 | width: 30, 7 | }, 8 | { 9 | title: '名称', 10 | key: 'name', 11 | width: 100, 12 | }, 13 | // { 14 | // title: '描述', 15 | // key: 'description', 16 | // width: 100, 17 | // }, 18 | { 19 | title: 'sha-1', 20 | key: 'sha_1', 21 | width: 150, 22 | }, 23 | { 24 | title: '大小', 25 | key: 'size', 26 | width: 100, 27 | render(row) { 28 | return (row.size / 1000).toFixed(1) + "KB"; 29 | } 30 | }, 31 | { 32 | title: '上传时间', 33 | key: 'CreatedAt', 34 | width: 150, 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /frontend/src/views/stores/webshellList/columns.ts: -------------------------------------------------------------------------------- 1 | 2 | export const columns = [ 3 | { 4 | title: 'id', 5 | key: 'id', 6 | width: 30, 7 | }, 8 | { 9 | title: '名称', 10 | key: 'name', 11 | width: 100, 12 | }, 13 | // { 14 | // title: '描述', 15 | // key: 'description', 16 | // width: 100, 17 | // }, 18 | { 19 | title: 'sha-1', 20 | key: 'sha_1', 21 | width: 150, 22 | }, 23 | { 24 | title: '大小', 25 | key: 'size', 26 | width: 100, 27 | render(row) { 28 | return (row.size / 1000).toFixed(1) + "KB"; 29 | } 30 | }, 31 | { 32 | title: '上传时间', 33 | key: 'CreatedAt', 34 | width: 150, 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /frontend/src/views/system/role/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NTag } from 'naive-ui'; 3 | 4 | export const columns = [ 5 | { 6 | title: 'id', 7 | key: 'id', 8 | }, 9 | { 10 | title: '角色名称', 11 | key: 'name', 12 | }, 13 | { 14 | title: '说明', 15 | key: 'explain', 16 | }, 17 | { 18 | title: '是否默认角色', 19 | key: 'isDefault', 20 | render(row) { 21 | return h( 22 | NTag, 23 | { 24 | type: row.isDefault ? 'success' : 'error', 25 | }, 26 | { 27 | default: () => (row.isDefault ? '是' : '否'), 28 | } 29 | ); 30 | }, 31 | }, 32 | { 33 | title: '创建时间', 34 | key: 'create_date', 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /frontend/src/views/tools/SerializationDumper/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NTag } from 'naive-ui'; 3 | 4 | 5 | export const columns = [ 6 | { 7 | title: 'id', 8 | key: 'id', 9 | width: 30, 10 | }, 11 | { 12 | title: '文件名称', 13 | key: 'name', 14 | width: 40, 15 | render(row) { 16 | if (row.ForeignPayload != null){ 17 | return row.ForeignPayload.name; 18 | } 19 | } 20 | }, 21 | // { 22 | // title: '结果', 23 | // key: 'risk', 24 | // width: 80, 25 | // render(row) { 26 | // var risk; 27 | // switch(row.found) { 28 | 29 | // } 30 | // } 31 | // }, 32 | { 33 | title: '详情', 34 | key: 'result', 35 | width: 100, 36 | }, 37 | { 38 | title: '检测时间', 39 | key: 'CreatedAt', 40 | width: 100, 41 | }, 42 | { 43 | title: '状态', 44 | key: 'state', 45 | width: 50, 46 | render(row) { 47 | var state; 48 | switch(row.state) { 49 | case 0: { 50 | state = "空闲"; 51 | break; 52 | } 53 | case 1: { 54 | state = "运行"; 55 | break; 56 | } 57 | case 2: { 58 | state = "暂停"; 59 | break; 60 | } 61 | case 3: { 62 | state = "停止"; 63 | break; 64 | } 65 | case 4: { 66 | state = "完成"; 67 | break; 68 | } 69 | default: { 70 | state = "空闲"; 71 | break; 72 | } 73 | } 74 | return h( 75 | NTag, 76 | { 77 | type: state, 78 | }, 79 | { 80 | default: () => (state), 81 | } 82 | ); 83 | }, 84 | }, 85 | ]; 86 | 87 | 88 | -------------------------------------------------------------------------------- /frontend/src/views/tools/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 42 | 43 | -------------------------------------------------------------------------------- /frontend/src/views/tools/jq/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NTag } from 'naive-ui'; 3 | 4 | 5 | export const columns = [ 6 | { 7 | title: 'id', 8 | key: 'id', 9 | width: 30, 10 | }, 11 | { 12 | title: '文件名称', 13 | key: 'name', 14 | width: 40, 15 | render(row) { 16 | if (row.ForeignPayload != null){ 17 | return row.ForeignPayload.name; 18 | } 19 | } 20 | }, 21 | // { 22 | // title: '结果', 23 | // key: 'risk', 24 | // width: 80, 25 | // render(row) { 26 | // var risk; 27 | // switch(row.found) { 28 | 29 | // } 30 | // } 31 | // }, 32 | { 33 | title: '详情', 34 | key: 'result', 35 | width: 100, 36 | }, 37 | { 38 | title: '检测时间', 39 | key: 'CreatedAt', 40 | width: 100, 41 | }, 42 | { 43 | title: '状态', 44 | key: 'state', 45 | width: 50, 46 | render(row) { 47 | var state; 48 | switch(row.state) { 49 | case 0: { 50 | state = "空闲"; 51 | break; 52 | } 53 | case 1: { 54 | state = "运行"; 55 | break; 56 | } 57 | case 2: { 58 | state = "暂停"; 59 | break; 60 | } 61 | case 3: { 62 | state = "停止"; 63 | break; 64 | } 65 | case 4: { 66 | state = "完成"; 67 | break; 68 | } 69 | default: { 70 | state = "空闲"; 71 | break; 72 | } 73 | } 74 | return h( 75 | NTag, 76 | { 77 | type: state, 78 | }, 79 | { 80 | default: () => (state), 81 | } 82 | ); 83 | }, 84 | }, 85 | ]; 86 | 87 | 88 | -------------------------------------------------------------------------------- /frontend/src/views/tools/tshark/columns.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import { NTag } from 'naive-ui'; 3 | 4 | 5 | export const columns = [ 6 | { 7 | title: 'id', 8 | key: 'id', 9 | width: 30, 10 | }, 11 | { 12 | title: '文件名称', 13 | key: 'name', 14 | width: 40, 15 | render(row) { 16 | if (row.ForeignPayload != null){ 17 | return row.ForeignPayload.name; 18 | } 19 | } 20 | }, 21 | // { 22 | // title: '结果', 23 | // key: 'risk', 24 | // width: 80, 25 | // render(row) { 26 | // var risk; 27 | // switch(row.found) { 28 | 29 | // } 30 | // } 31 | // }, 32 | { 33 | title: '详情', 34 | key: 'result', 35 | width: 100, 36 | }, 37 | { 38 | title: '检测时间', 39 | key: 'CreatedAt', 40 | width: 100, 41 | }, 42 | { 43 | title: '状态', 44 | key: 'state', 45 | width: 50, 46 | render(row) { 47 | var state; 48 | switch(row.state) { 49 | case 0: { 50 | state = "空闲"; 51 | break; 52 | } 53 | case 1: { 54 | state = "运行"; 55 | break; 56 | } 57 | case 2: { 58 | state = "暂停"; 59 | break; 60 | } 61 | case 3: { 62 | state = "停止"; 63 | break; 64 | } 65 | case 4: { 66 | state = "完成"; 67 | break; 68 | } 69 | default: { 70 | state = "空闲"; 71 | break; 72 | } 73 | } 74 | return h( 75 | NTag, 76 | { 77 | type: state, 78 | }, 79 | { 80 | default: () => (state), 81 | } 82 | ); 83 | }, 84 | }, 85 | ]; 86 | 87 | 88 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strictFunctionTypes": false, 10 | "jsx": "preserve", 11 | "baseUrl": ".", 12 | "allowJs": true, 13 | "sourceMap": true, 14 | "esModuleInterop": true, 15 | "resolveJsonModule": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "experimentalDecorators": true, 19 | "lib": [ 20 | "dom", 21 | "esnext" 22 | ], 23 | "types": [ 24 | "vite/client" 25 | ], 26 | "typeRoots": [ 27 | "./node_modules/@types/", 28 | "./types" 29 | ], 30 | "noImplicitAny": false, 31 | "skipLibCheck": true, 32 | "paths": { 33 | "@/*": [ 34 | "src/*" 35 | ], 36 | "/#/*": [ 37 | "types/*" 38 | ] 39 | } 40 | }, 41 | "include": [ 42 | "src/**/*.ts", 43 | "src/**/*.d.ts", 44 | "src/**/*.tsx", 45 | "src/**/*.vue", 46 | "types/**/*.d.ts", 47 | "types/**/*.ts", 48 | "build/**/*.ts", 49 | "build/**/*.d.ts", 50 | "mock/**/*.ts", 51 | "components.d.ts", 52 | "vite.config.ts" 53 | ], 54 | "exclude": [ 55 | "node_modules", 56 | "dist", 57 | "**/*.js" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /frontend/types/config.d.ts: -------------------------------------------------------------------------------- 1 | export interface ProjectSettingState { 2 | //导航模式 3 | navMode: string; 4 | //导航风格 5 | navTheme: string; 6 | //顶部设置 7 | headerSetting: object; 8 | //页脚 9 | showFooter: boolean; 10 | //菜单设置 11 | menuSetting: object; 12 | //多标签 13 | multiTabsSetting: object; 14 | //面包屑 15 | crumbsSetting: object; 16 | //权限模式 17 | permissionMode: string; 18 | } 19 | 20 | export interface IbodySetting { 21 | fixed: boolean; 22 | } 23 | 24 | export interface IheaderSetting { 25 | bgColor: string; 26 | fixed: boolean; 27 | isReload: boolean; 28 | } 29 | 30 | export interface ImenuSetting { 31 | minMenuWidth: number; 32 | menuWidth: number; 33 | fixed: boolean; 34 | mixMenu: boolean; 35 | collapsed: boolean; 36 | mobileWidth: number; 37 | } 38 | 39 | export interface IcrumbsSetting { 40 | show: boolean; 41 | showIcon: boolean; 42 | } 43 | 44 | export interface ImultiTabsSetting { 45 | bgColor: string; 46 | fixed: boolean; 47 | show: boolean; 48 | } 49 | export interface GlobConfig { 50 | title: string; 51 | apiUrl: string; 52 | shortName: string; 53 | urlPrefix?: string; 54 | uploadUrl?: string; 55 | prodMock: boolean; 56 | imgUrl?: string; 57 | } 58 | 59 | export interface GlobEnvConfig { 60 | // 标题 61 | VITE_GLOB_APP_TITLE: string; 62 | // 接口地址 63 | VITE_GLOB_API_URL: string; 64 | // 接口前缀 65 | VITE_GLOB_API_URL_PREFIX?: string; 66 | // Project abbreviation 67 | VITE_GLOB_APP_SHORT_NAME: string; 68 | // 图片上传地址 69 | VITE_GLOB_UPLOAD_URL?: string; 70 | //图片前缀地址 71 | VITE_GLOB_IMG_URL?: string; 72 | //生产环境开启mock 73 | VITE_GLOB_PROD_MOCK: boolean; 74 | } 75 | -------------------------------------------------------------------------------- /frontend/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Fn { 2 | (...arg: T[]): R; 3 | } 4 | 5 | declare interface PromiseFn { 6 | (...arg: T[]): Promise; 7 | } 8 | 9 | declare type RefType = T | null; 10 | 11 | declare type LabelValueOptions = { 12 | label: string; 13 | value: any; 14 | disabled: boolean; 15 | [key: string]: string | number | boolean; 16 | }[]; 17 | 18 | declare type EmitType = (event: string, ...args: any[]) => void; 19 | 20 | declare type TargetContext = '_self' | '_blank'; 21 | 22 | declare interface ComponentElRef { 23 | $el: T; 24 | } 25 | 26 | declare type ComponentRef = ComponentElRef | null; 27 | 28 | declare type ElRef = Nullable; 29 | -------------------------------------------------------------------------------- /frontend/types/modules.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue'; 3 | const Component: DefineComponent<{}, {}, any>; 4 | export default Component; 5 | } 6 | 7 | declare module 'virtual:*' { 8 | const result: any; 9 | export default result; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /frontend/types/utils.d.ts: -------------------------------------------------------------------------------- 1 | import type { ComputedRef, Ref } from 'vue'; 2 | 3 | export type DynamicProps = { 4 | [P in keyof T]: Ref | T[P] | ComputedRef; 5 | }; 6 | -------------------------------------------------------------------------------- /investigation/S2_log4j/log4j_test.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/investigation/S2_log4j/log4j_test.pcap -------------------------------------------------------------------------------- /investigation/S2_log4j/log4j_test2.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/investigation/S2_log4j/log4j_test2.pcap -------------------------------------------------------------------------------- /investigation/doc/2022-11-25-20-04-34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/investigation/doc/2022-11-25-20-04-34.png -------------------------------------------------------------------------------- /investigation/doc/2022-11-25-20-13-39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/investigation/doc/2022-11-25-20-13-39.png -------------------------------------------------------------------------------- /investigation/doc/2022-11-25-21-34-37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/investigation/doc/2022-11-25-21-34-37.png -------------------------------------------------------------------------------- /investigation/doc/2022-11-25-21-36-46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/investigation/doc/2022-11-25-21-36-46.png -------------------------------------------------------------------------------- /investigation/doc/2022-11-25-21-45-54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/investigation/doc/2022-11-25-21-45-54.png -------------------------------------------------------------------------------- /investigation/doc/Snipaste_2024-07-03_19-18-35.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/investigation/doc/Snipaste_2024-07-03_19-18-35.jpg -------------------------------------------------------------------------------- /investigation/rpc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martin2877/btab/846e034ffa57413988e30ae5172f716a020a2aee/investigation/rpc/__init__.py -------------------------------------------------------------------------------- /investigation/rpc/msg_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: msg.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import descriptor_pool as _descriptor_pool 7 | from google.protobuf import message as _message 8 | from google.protobuf import reflection as _reflection 9 | from google.protobuf import symbol_database as _symbol_database 10 | # @@protoc_insertion_point(imports) 11 | 12 | _sym_db = _symbol_database.Default() 13 | 14 | 15 | 16 | 17 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tmsg.proto\x12\x03rpc\"K\n\x0cResponseType\x12\x0c\n\x04\x43ode\x18\x01 \x01(\x03\x12\x0f\n\x07Message\x18\x02 \x01(\t\x12\x0e\n\x06Result\x18\x03 \x01(\t\x12\x0c\n\x04Type\x18\x04 \x01(\tB\x04Z\x02./b\x06proto3') 18 | 19 | 20 | 21 | _RESPONSETYPE = DESCRIPTOR.message_types_by_name['ResponseType'] 22 | ResponseType = _reflection.GeneratedProtocolMessageType('ResponseType', (_message.Message,), { 23 | 'DESCRIPTOR' : _RESPONSETYPE, 24 | '__module__' : 'msg_pb2' 25 | # @@protoc_insertion_point(class_scope:rpc.ResponseType) 26 | }) 27 | _sym_db.RegisterMessage(ResponseType) 28 | 29 | if _descriptor._USE_C_DESCRIPTORS == False: 30 | 31 | DESCRIPTOR._options = None 32 | DESCRIPTOR._serialized_options = b'Z\002./' 33 | _RESPONSETYPE._serialized_start=18 34 | _RESPONSETYPE._serialized_end=93 35 | # @@protoc_insertion_point(module_scope) 36 | -------------------------------------------------------------------------------- /investigation/rpc/msg_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | --------------------------------------------------------------------------------