From f19b1be9d433d4d84e9299e3c3f0fd11c5b9385b Mon Sep 17 00:00:00 2001 From: ReaJason Date: Sat, 27 Jun 2026 14:54:37 +0800 Subject: [PATCH] feat: support select child packer --- .../boot/controller/ConfigController.java | 16 + .../reajason/javaweb/boot/vo/PackerVO.java | 10 + .../memshell/package-config-card.tsx | 38 +- .../memshell/results/result-component.tsx | 36 +- web/app/components/packer-selector.tsx | 194 ++++ .../probeshell/package-config-card.tsx | 56 +- web/app/components/ui/scroll-area.tsx | 53 ++ web/app/i18n/common/en.json | 7 + web/app/i18n/common/zh-CN.json | 7 + web/app/routes/memshell.tsx | 2 +- web/app/routes/probeshell.tsx | 2 +- web/app/types/memshell.ts | 7 +- web/app/utils/transformer.ts | 36 +- web/bun.lock | 832 ++++++++++-------- web/package.json | 52 +- web/react-router.config.ts | 3 - 16 files changed, 842 insertions(+), 509 deletions(-) create mode 100644 boot/src/main/java/com/reajason/javaweb/boot/vo/PackerVO.java create mode 100644 web/app/components/packer-selector.tsx create mode 100644 web/app/components/ui/scroll-area.tsx diff --git a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java index 35bd6384..8c789664 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java @@ -1,6 +1,7 @@ package com.reajason.javaweb.boot.controller; import com.reajason.javaweb.boot.vo.CommandConfigVO; +import com.reajason.javaweb.boot.vo.PackerVO; import com.reajason.javaweb.memshell.ServerFactory; import com.reajason.javaweb.memshell.config.CommandConfig; import com.reajason.javaweb.memshell.server.AbstractServer; @@ -40,6 +41,21 @@ public class ConfigController { .map(Packers::name).toList(); } + /** + * 返回父/子 packer 层级结构,供前端在「父模式 / 子模式」之间选择。 + * 单独新增端点而非修改 {@link #getPackers()},以避免破坏旧版本前端对返回值的依赖。 + */ + @RequestMapping("/packers/tree") + public List getPackerTree() { + return Arrays.stream(Packers.values()) + .filter(packers -> packers.getParentPacker() == null) + .map(packers -> new PackerVO( + packers.name(), + Packers.getPackersWithParent(packers.getInstance().getClass()) + .stream().map(Packers::name).toList())) + .toList(); + } + @RequestMapping public Map> config() { Map> coreMap = new HashMap<>(16); diff --git a/boot/src/main/java/com/reajason/javaweb/boot/vo/PackerVO.java b/boot/src/main/java/com/reajason/javaweb/boot/vo/PackerVO.java new file mode 100644 index 00000000..d9339d16 --- /dev/null +++ b/boot/src/main/java/com/reajason/javaweb/boot/vo/PackerVO.java @@ -0,0 +1,10 @@ +package com.reajason.javaweb.boot.vo; + +import java.util.List; + +/** + * @author ReaJason + * @since 2026/6/27 + */ +public record PackerVO(String name, List children) { +} diff --git a/web/app/components/memshell/package-config-card.tsx b/web/app/components/memshell/package-config-card.tsx index a86ad746..a14d119f 100644 --- a/web/app/components/memshell/package-config-card.tsx +++ b/web/app/components/memshell/package-config-card.tsx @@ -1,4 +1,4 @@ -import type { PackerConfig } from "@/types/memshell"; +import type { PackerConfig, PackerOption } from "@/types/memshell"; import type { MemShellFormSchema } from "@/types/schema"; import { PackageIcon } from "lucide-react"; @@ -6,9 +6,8 @@ import { useMemo } from "react"; import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import PackerSelector from "@/components/packer-selector"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { FieldLabel, FieldSet } from "@/components/ui/field"; -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Spinner } from "@/components/ui/spinner"; export default function PackageConfigCard({ @@ -30,8 +29,8 @@ export default function PackageConfigCard({ name: "server", }); - const options = useMemo(() => { - const filteredOptions = (packerConfig ?? []).filter((name) => { + const parents = useMemo(() => { + return (packerConfig ?? []).filter(({ name }) => { if (!shellType || shellType === " ") { return true; } @@ -43,12 +42,7 @@ export default function PackageConfigCard({ } return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl"); }); - form.setValue("packingMethod", filteredOptions[0]); - return filteredOptions.map((name) => ({ - name: t(name), - value: name, - })); - }, [packerConfig, shellType, server, t, form]); + }, [packerConfig, shellType, server]); return ( @@ -59,30 +53,12 @@ export default function PackageConfigCard({ - {options.length > 0 ? ( + {parents.length > 0 ? ( ( -
- {t("packerMethod")} - - {options.map(({ name, value }) => ( -
- - - {name} - -
- ))} -
-
+ )} /> ) : ( diff --git a/web/app/components/memshell/results/result-component.tsx b/web/app/components/memshell/results/result-component.tsx index 1408bcd7..e7cf7de3 100644 --- a/web/app/components/memshell/results/result-component.tsx +++ b/web/app/components/memshell/results/result-component.tsx @@ -27,6 +27,24 @@ export function ResultComponent({ const isAgent = packMethod.startsWith("Agent"); const isJar = packMethod.endsWith("Jar"); const { t } = useTranslation(); + const shellClassName = generateResult?.shellClassName; + + const handleDownload = useCallback(() => { + const fileName = shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? ""; + if (packMethod.includes("JSP")) { + const fileExtension = packMethod.includes("JSPX") ? ".jspx" : ".jsp"; + const content = new Blob([packResult as string], { type: "text/plain" }); + return downloadContent(content, fileName, fileExtension); + } else if (packMethod.includes("JavaCommons") || packMethod.includes("Hessian")) { + const content = new Blob([base64ToBytes(packResult as string)], { + type: "application/octet-stream", + }); + return downloadContent(content, fileName, ".data"); + } else if (packMethod === "Base64") { + return downloadBytes(packResult as string, shellClassName); + } + }, [packMethod, packResult, shellClassName]); + if (allPackResults) { return ( { - const fileName = shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? ""; - if (packMethod.includes("JSP")) { - const fileExtension = packMethod.includes("JSPX") ? ".jspx" : ".jsp"; - const content = new Blob([packResult as string], { type: "text/plain" }); - return downloadContent(content, fileName, fileExtension); - } else if (packMethod.includes("JavaCommons") || packMethod.includes("Hessian")) { - const content = new Blob([base64ToBytes(packResult as string)], { - type: "application/octet-stream", - }); - return downloadContent(content, fileName, ".data"); - } else if (packMethod === "Base64") { - return downloadBytes(packResult as string, shellClassName); - } - }, [packMethod, packResult, shellClassName]); - return ( ) { + return ( +