feat: support multi payload generate

This commit is contained in:
ReaJason
2025-01-27 17:02:32 +08:00
parent 8d7ed8c4ee
commit 3efe814d30
43 changed files with 537 additions and 192 deletions
+48 -40
View File
@@ -1,7 +1,6 @@
import { Button, ButtonProps } from "@/components/ui/button.tsx";
import { cn } from "@/lib/utils.ts";
import { Check, Copy } from "lucide-react";
import { HTMLProps, useEffect, useState } from "react";
import { HTMLProps, ReactNode, useEffect, useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism";
@@ -31,15 +30,8 @@ export function CopyButton({ value, className, src, variant = "ghost", ...props
toast.success("复制成功", { duration: 1000 });
}}
>
<Button
size="icon"
type="button"
variant={variant}
className={cn("relative z-10 h-8 w-8 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50", className)}
{...props}
>
<span className="sr-only">Copy</span>
{hasCopied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
<Button variant="ghost" size="icon" type="button" className="h-7 w-7 [&_svg]:h-4 [&_svg]:w-4">
{hasCopied ? <Check /> : <Copy />}
</Button>
</CopyToClipboard>
);
@@ -47,41 +39,57 @@ export function CopyButton({ value, className, src, variant = "ghost", ...props
export function CodeViewer({
code,
header,
language,
height,
showLineNumbers = true,
wrapLongLines = false,
height = 500,
}: {
code: string;
language: string;
showLineNumbers?: boolean;
wrapLongLines?: boolean;
height?: number;
}) {
wrapLongLines = true,
}: CodeViewerProps) {
const lineProps: lineTagPropsFunction | HTMLProps<HTMLElement> | undefined = wrapLongLines
? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } }
: undefined;
return (
<div className="relative overflow-hidden text-xs wrap-all">
<CopyButton value={code} className="absolute right-4 top-2" />
<SyntaxHighlighter
language={language}
style={materialDark}
showLineNumbers={showLineNumbers}
wrapLongLines={wrapLongLines}
lineProps={lineProps}
customStyle={{
margin: 0,
paddingRight: showLineNumbers ? 0 : 24,
paddingLeft: showLineNumbers ? 0 : 24,
borderRadius: "var(--radius)",
height: height,
whiteSpace: wrapLongLines ? "pre-wrap" : "pre",
overflowWrap: wrapLongLines ? "normal" : "break-word",
}}
>
{code}
</SyntaxHighlighter>
<div className="rounded-lg border">
{header && (
<div className="flex items-center justify-between border-b p-2">
{header}
<CopyButton value={code} variant="ghost" size="sm" />
</div>
)}
{!header && (
<div className="flex items-center justify-end border-b p-2">
<CopyButton value={code} variant="ghost" size="sm" />
</div>
)}
<div className="relative overflow-hidden text-xs wrap-all">
<SyntaxHighlighter
language={language}
style={materialDark}
showLineNumbers={showLineNumbers}
wrapLongLines={wrapLongLines}
lineProps={lineProps}
customStyle={{
margin: 0,
padding: showLineNumbers ? 0 : "1em 1em",
borderRadius: "0 0 var(--radius) var(--radius)",
height: height,
whiteSpace: wrapLongLines ? "pre-wrap" : "pre",
overflowWrap: wrapLongLines ? "normal" : "break-word",
}}
>
{code}
</SyntaxHighlighter>
</div>
</div>
);
}
interface CodeViewerProps {
code: string;
language: string;
header?: ReactNode;
height?: string | number;
showLineNumbers?: boolean;
wrapLongLines?: boolean;
lineProps?: (lineNumber: number) => React.HTMLProps<HTMLElement>;
}
+10 -12
View File
@@ -12,19 +12,17 @@ interface CopyableFieldProps {
}
export function CopyableField({ label, value, text }: CopyableFieldProps) {
const [copied, setCopied] = useState(false);
const [hasCopied, setHasCopied] = useState(false);
const copyToClipboard = useCallback(() => {
if (typeof value === "string") {
navigator.clipboard.writeText(value).then(() => {
setCopied(true);
toast.success(`复制${label}成功`, {
duration: 1000,
});
setTimeout(() => setCopied(false), 1000);
});
}
}, [value, label]);
setHasCopied(true);
toast.success(`复制${label}成功`, {
duration: 1000,
});
setTimeout(() => {
setHasCopied(false);
}, 1000);
}, [label]);
return (
<div className="flex items-center justify-between h-9">
@@ -35,7 +33,7 @@ export function CopyableField({ label, value, text }: CopyableFieldProps) {
{value && (
<CopyToClipboard text={value as string} onCopy={copyToClipboard}>
<Button variant="ghost" size="icon" type="button">
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
{hasCopied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
</Button>
</CopyToClipboard>
)}
+109 -19
View File
@@ -15,6 +15,7 @@ import {
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx";
import { Button } from "@/components/ui/button.tsx";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator.tsx";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx";
import { downloadBytes } from "@/lib/utils.ts";
@@ -24,8 +25,9 @@ import {
GenerateResult,
GodzillaShellToolConfig,
} from "@/types/shell.ts";
import { TFunction } from "i18next";
import { CircleHelpIcon, TriangleAlertIcon } from "lucide-react";
import { Fragment } from "react";
import { Fragment, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
@@ -219,14 +221,111 @@ function BasicInfo({ generateResult }: { generateResult?: GenerateResult }) {
);
}
export function ShellResult({
packResult,
function MultiPackResult({
allPackResults,
packMethod,
generateResult,
}: { packResult: string; packMethod: string; generateResult?: GenerateResult }) {
t,
}: {
allPackResults: object | undefined;
packMethod: string;
t: TFunction;
}) {
const showCode = packMethod === "JSP";
const packMethods = Object.keys(allPackResults ?? {});
const [selectedMethod, setSelectedMethod] = useState(packMethods[0]);
const [packResult, setPackResult] = useState(allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "");
return (
<Fragment>
<CodeViewer
code={packResult ?? ""}
header={
<div className="flex items-center justify-between text-xs gap-2">
<Select
onValueChange={(value) => {
setSelectedMethod(value);
setPackResult(allPackResults?.[value as keyof typeof allPackResults] ?? "");
}}
value={selectedMethod}
>
<SelectTrigger className="h-7 text-xs [&_svg]:h-4 [&_svg]:w-4">
<span className="text-muted-foreground">{t("packageConfig.title")}:&nbsp;</span>
<SelectValue />
</SelectTrigger>
<SelectContent>
{packMethods.map((method) => (
<SelectItem key={method} value={method} className="text-xs">
{method}
</SelectItem>
))}
</SelectContent>
</Select>
<span className="text-muted-foreground">({packResult?.length})</span>
</div>
}
wrapLongLines={!showCode}
showLineNumbers={showCode}
language={showCode ? "java" : "text"}
height={350}
/>
</Fragment>
);
}
function renderResultComponent(
packResult: string | undefined,
allPackResults: Map<string, string> | undefined,
packMethod: string,
t: TFunction,
generateResult?: GenerateResult,
) {
const showCode = packMethod === "JSP";
const isAgent = packMethod.startsWith("Agent");
const isJar = packMethod === "Jar";
if (allPackResults) {
return <MultiPackResult allPackResults={allPackResults} packMethod={packMethod} t={t} />;
}
if (isAgent) {
return <AgentResult packResult={packResult ?? ""} generateResult={generateResult} />;
}
if (isJar) {
return <JarResult packResult={packResult ?? ""} generateResult={generateResult} />;
}
if (!isAgent && !isJar) {
return (
<Fragment>
<CodeViewer
code={packResult ?? ""}
header={
<div className="flex items-center justify-between text-xs gap-2">
<span>
{t("packageConfig.title")}{packMethod}
</span>
<span className="text-muted-foreground">({packResult?.length})</span>
</div>
}
wrapLongLines={!showCode}
showLineNumbers={showCode}
language={showCode ? "java" : "text"}
height={350}
/>
</Fragment>
);
}
return null;
}
export function ShellResult({
packResult,
allPackResults,
packMethod,
generateResult,
}: {
packResult: string | undefined;
allPackResults: Map<string, string> | undefined;
packMethod: string;
generateResult?: GenerateResult;
}) {
const { t } = useTranslation();
return (
<Fragment>
@@ -241,20 +340,7 @@ export function ShellResult({
<div className="mb-4">
<BasicInfo generateResult={generateResult} />
</div>
{!isAgent && !isJar && (
<Fragment>
<div className="flex items-center justify-end text-sm text-muted-foreground">({packResult.length})</div>
<CodeViewer
code={packResult}
wrapLongLines={!showCode}
showLineNumbers={showCode}
language={showCode ? "java" : "text"}
height={400}
/>
</Fragment>
)}
{isAgent && <AgentResult packResult={packResult} generateResult={generateResult} />}
{isJar && <JarResult packResult={packResult} generateResult={generateResult} />}
{renderResultComponent(packResult, allPackResults, packMethod, t, generateResult)}
</TabsContent>
<TabsContent value="shell" className="mt-4">
<Alert>
@@ -281,7 +367,9 @@ export function ShellResult({
</div>
<CodeViewer
showLineNumbers={false}
header={<div className="text-xs">{generateResult?.shellClassName}</div>}
wrapLongLines={true}
height={600}
code={generateResult?.shellBytesBase64Str ?? ""}
language="text"
/>
@@ -312,6 +400,8 @@ export function ShellResult({
<CodeViewer
showLineNumbers={false}
wrapLongLines={true}
header={<div className="text-xs">{generateResult?.injectorClassName}</div>}
height={600}
code={generateResult?.injectorBytesBase64Str ?? ""}
language="text"
/>
+1 -1
View File
@@ -60,7 +60,7 @@ export const resources = {
AgentJar: "AgentJar",
Deserialize: "Deserialize(Only CB4, 1.9.x)",
ScriptEngine: "ScriptEngine",
XxlJob: "XXL-JOB Executor"
XxlJob: "XXL-JOB Executor",
},
},
tips: {
+9 -2
View File
@@ -53,7 +53,8 @@ function IndexComponent() {
},
});
const [packResult, setPackResult] = useState<string>("");
const [packResult, setPackResult] = useState<string | undefined>();
const [allPackResults, setAllPackResults] = useState<Map<string, string> | undefined>();
const [generateResult, setGenerateResult] = useState<GenerateResult>();
const [packMethod, setPackMethod] = useState<string>("");
const [isActionPending, startTransition] = useTransition();
@@ -98,6 +99,7 @@ function IndexComponent() {
if (response.ok) {
const json: GenerateResponse = await response.json();
setPackResult(json.packResult);
setAllPackResults(json.allPackResults);
setGenerateResult(json.generateResult);
setPackMethod(values.packingMethod);
toast.success(t("success.generated"));
@@ -124,7 +126,12 @@ function IndexComponent() {
</Button>
</div>
<div className="w-full xl:w-1/2 space-y-4">
<ShellResult packMethod={packMethod} generateResult={generateResult} packResult={packResult} />
<ShellResult
packMethod={packMethod}
generateResult={generateResult}
packResult={packResult}
allPackResults={allPackResults}
/>
</div>
</form>
</Form>
+2 -1
View File
@@ -61,8 +61,9 @@ export interface MainConfig {
export type PackerConfig = Array<string>;
export interface GenerateResponse {
packResult: string;
generateResult: GenerateResult;
packResult?: string;
allPackResults?: Map<string, string>;
}
export interface APIErrorResponse {