mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 23:11:52 +08:00
feat: support fumadocs
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { FileTextIcon } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CopyableField } from "@/components/copyable-field";
|
||||
import { FeedbackAlert } from "@/components/memshell/results/feedback-alert";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import type { ProbeShellResult } from "@/types/probeshell";
|
||||
|
||||
export function BasicInfo({
|
||||
generateResult,
|
||||
}: Readonly<{ generateResult?: ProbeShellResult }>) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<div className="text-md flex items-center gap-2">
|
||||
<FileTextIcon className="h-5" />
|
||||
<span>{t("common:basicInfo")}</span>
|
||||
</div>
|
||||
<FeedbackAlert />
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
<CopyableField
|
||||
label={t("probeshell:shellClassName")}
|
||||
value={generateResult?.shellClassName}
|
||||
text={`${generateResult?.shellClassName} (${generateResult?.shellSize} bytes)`}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
import { ServerIcon } from "lucide-react";
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
import { FormProvider, type UseFormReturn } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormFieldItem,
|
||||
FormFieldLabel,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import type { ServerConfig } from "@/types/memshell";
|
||||
import type { ProbeShellFormSchema } from "@/types/schema";
|
||||
import { Separator } from "../ui/separator";
|
||||
|
||||
const PROBE_OPTIONS = [
|
||||
{ value: "Server" as const, label: "server" },
|
||||
{ value: "JDK" as const, label: "jdk" },
|
||||
{ value: "Command" as const, label: "command" },
|
||||
{ value: "Bytecode" as const, label: "bytecode" },
|
||||
{ value: "ScriptEngine" as const, label: "script" },
|
||||
] as const;
|
||||
|
||||
const MIDDLEWARE_OPTIONS = [
|
||||
{ value: "Tomcat", label: "Tomcat" },
|
||||
{ value: "Jetty", label: "Jetty" },
|
||||
{ value: "Undertow", label: "Undertow" },
|
||||
{ value: "Resin", label: "Resin" },
|
||||
{ value: "JBoss", label: "JBoss" },
|
||||
{ value: "GlassFish", label: "GlassFish" },
|
||||
{ value: "BES", label: "BES" },
|
||||
{ value: "TongWeb", label: "TongWeb" },
|
||||
{ value: "InforSuite", label: "InforSuite" },
|
||||
{ value: "Apusic", label: "Apusic" },
|
||||
{ value: "SpringWebFlux", label: "SpringWebFlux" },
|
||||
{ value: "WebLogic", label: "WebLogic" },
|
||||
{ value: "WebSphere", label: "WebSphere" },
|
||||
] as const;
|
||||
|
||||
const PROBE_METHOD_OPTIONS = [
|
||||
{ value: "Sleep", label: "Sleep" },
|
||||
{ value: "DNSLog", label: "DNSLog" },
|
||||
{ value: "ResponseBody", label: "ResponseBody" },
|
||||
] as const;
|
||||
|
||||
const DEFAULT_FORM_VALUES = {
|
||||
reqParamName: "payload",
|
||||
sleepServer: "Tomcat",
|
||||
seconds: 5,
|
||||
} as const;
|
||||
|
||||
interface MainConfigCardProps {
|
||||
readonly form: UseFormReturn<ProbeShellFormSchema>;
|
||||
readonly servers?: ServerConfig;
|
||||
}
|
||||
|
||||
export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
|
||||
const { t } = useTranslation(["common", "probeshell"]);
|
||||
const watchedProbeMethod = form.watch("probeMethod");
|
||||
const watchedProbeContent = form.watch("probeContent");
|
||||
|
||||
const filteredOptions = useMemo(() => {
|
||||
const filterMap = {
|
||||
ResponseBody: ["Command", "Bytecode", "ScriptEngine"],
|
||||
DNSLog: ["JDK", "Server"],
|
||||
Sleep: ["Server"],
|
||||
} as const;
|
||||
|
||||
const allowedValues =
|
||||
filterMap[watchedProbeMethod as keyof typeof filterMap];
|
||||
|
||||
if (!allowedValues) return PROBE_OPTIONS;
|
||||
|
||||
return PROBE_OPTIONS.filter((opt) =>
|
||||
allowedValues.includes(opt.value as never),
|
||||
);
|
||||
}, [watchedProbeMethod]);
|
||||
|
||||
const resetFormValues = useCallback(() => {
|
||||
if (filteredOptions.length === 0) return;
|
||||
|
||||
const currentValues = form.getValues();
|
||||
form.reset({
|
||||
...currentValues,
|
||||
probeMethod: watchedProbeMethod,
|
||||
probeContent: filteredOptions[0].value,
|
||||
...DEFAULT_FORM_VALUES,
|
||||
});
|
||||
}, [form, watchedProbeMethod, filteredOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
resetFormValues();
|
||||
}, [resetFormValues]);
|
||||
|
||||
const ContentOptionsSelect = useMemo(
|
||||
() => (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="probeContent"
|
||||
render={({ field }) => (
|
||||
<FormFieldItem>
|
||||
<FormFieldLabel>{t("probeshell:probeContent")}</FormFieldLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value || ""}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("common:placeholders.select")} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{filteredOptions.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
{t(`probeshell:probeContent.${opt.label}`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormFieldItem>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
[form.control, filteredOptions, t],
|
||||
);
|
||||
|
||||
const RequestParamField = useMemo(
|
||||
() => (
|
||||
<div className="space-y-2 pt-4 border-t mt-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reqParamName"
|
||||
render={({ field }) => (
|
||||
<FormFieldItem>
|
||||
<FormFieldLabel>{t("common:paramName")}</FormFieldLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("placeholders.input")} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormFieldItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
[form.control, t],
|
||||
);
|
||||
|
||||
const SleepFields = useMemo(
|
||||
() => (
|
||||
<div className="space-y-2 pt-4 border-t mt-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sleepServer"
|
||||
render={({ field }) => (
|
||||
<FormFieldItem>
|
||||
<FormFieldLabel>{t("probeshell:sleepServer")}</FormFieldLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value || ""}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("placeholders.select")} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{MIDDLEWARE_OPTIONS.map(({ value, label }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormFieldItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="seconds"
|
||||
render={({ field }) => (
|
||||
<FormFieldItem>
|
||||
<FormFieldLabel>{t("probeshell:sleepSeconds")}</FormFieldLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder={t("placeholders.input")}
|
||||
{...field}
|
||||
onChange={(event) => field.onChange(+event.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormFieldItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
[form.control, t],
|
||||
);
|
||||
|
||||
const renderDynamicFields = useCallback(() => {
|
||||
const isBodyMethod = watchedProbeMethod === "ResponseBody";
|
||||
const needParam =
|
||||
watchedProbeContent === "Command" ||
|
||||
watchedProbeContent === "Bytecode" ||
|
||||
watchedProbeContent === "ScriptEngine";
|
||||
const isSleepMethod = watchedProbeMethod === "Sleep";
|
||||
const isServerContent = watchedProbeContent === "Server";
|
||||
|
||||
if (isBodyMethod && needParam) {
|
||||
return RequestParamField;
|
||||
}
|
||||
|
||||
if (isSleepMethod && isServerContent) {
|
||||
return SleepFields;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [watchedProbeMethod, watchedProbeContent, RequestParamField, SleepFields]);
|
||||
|
||||
const DNSLogSection = useMemo(
|
||||
() => (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="host"
|
||||
render={({ field }) => (
|
||||
<FormFieldItem>
|
||||
<FormFieldLabel>{t("probeshell:dnslog.host")}</FormFieldLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("placeholders.input")} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormFieldItem>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
[form.control, t],
|
||||
);
|
||||
|
||||
const MiddlewareSelect = useMemo(
|
||||
() => (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="server"
|
||||
render={({ field }) => (
|
||||
<FormFieldItem>
|
||||
<FormFieldLabel>{t("server")}</FormFieldLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t("placeholders.select")} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.keys(servers ?? {})
|
||||
.filter((s) => s !== "SpringWebFlux" && s !== "XXLJOB")
|
||||
.map((server: string) => (
|
||||
<SelectItem key={server} value={server}>
|
||||
{server}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormFieldItem>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
[form.control, servers, t],
|
||||
);
|
||||
|
||||
const SwitchGroup = useMemo(
|
||||
() => (
|
||||
<div className="flex gap-4 mt-4 flex-col sm:flex-row">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="debug"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
id="debug"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel htmlFor="debug">{t("debug")}</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="byPassJavaModule"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
id="bypass"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<Label htmlFor="bypass">{t("byPassJavaModule")}</Label>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="shrink"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
id="shrink"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<Label htmlFor="shrink">{t("shrink")}</Label>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="staticInitialize"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
id="staticInitialize"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<Label htmlFor="staticInitialize">
|
||||
{t("common:staticInitialize")}
|
||||
</Label>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
[form.control, t],
|
||||
);
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Card>
|
||||
<CardHeader className="pb-1">
|
||||
<CardTitle className="text-md flex items-center gap-2">
|
||||
<ServerIcon className="h-5 w-5" />
|
||||
<span>{t("mainConfig.title")}</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="probeMethod"
|
||||
render={({ field }) => (
|
||||
<FormFieldItem>
|
||||
<FormFieldLabel>{t("probeshell:probeMethod")}</FormFieldLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{PROBE_METHOD_OPTIONS.map(({ value, label }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormFieldItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{watchedProbeMethod === "ResponseBody" && MiddlewareSelect}
|
||||
{watchedProbeMethod === "DNSLog" && DNSLogSection}
|
||||
{watchedProbeMethod && ContentOptionsSelect}
|
||||
{SwitchGroup}
|
||||
{renderDynamicFields()}
|
||||
<Separator />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="shellClassName"
|
||||
render={({ field }) => (
|
||||
<FormFieldItem>
|
||||
<FormFieldLabel htmlFor="shellClassName">
|
||||
{t("probeshell:shellClassName")} {t("optional")}
|
||||
</FormFieldLabel>
|
||||
<Input
|
||||
id="shellClassName"
|
||||
{...field}
|
||||
placeholder={t("placeholders.input")}
|
||||
/>
|
||||
</FormFieldItem>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { PackageIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormProvider, type UseFormReturn } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from "@/components/ui/form";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import type { PackerConfig } from "@/types/memshell";
|
||||
import type { ProbeShellFormSchema } from "@/types/schema";
|
||||
|
||||
type Option = {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export default function PackageConfigCard({
|
||||
packerConfig,
|
||||
form,
|
||||
}: Readonly<{
|
||||
packerConfig: PackerConfig | undefined;
|
||||
form: UseFormReturn<ProbeShellFormSchema>;
|
||||
}>) {
|
||||
const [options, setOptions] = useState<Array<Option>>([]);
|
||||
const { t } = useTranslation("common");
|
||||
|
||||
useEffect(() => {
|
||||
const filteredOptions = (packerConfig ?? []).filter((name) => {
|
||||
return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl");
|
||||
});
|
||||
|
||||
const mappedOptions = filteredOptions.map((name) => {
|
||||
return {
|
||||
name: name,
|
||||
value: name,
|
||||
};
|
||||
});
|
||||
|
||||
setOptions(mappedOptions);
|
||||
const currentValue = form.getValues("packingMethod");
|
||||
if (
|
||||
filteredOptions.length > 0 &&
|
||||
(!currentValue || !filteredOptions.includes(currentValue))
|
||||
) {
|
||||
form.setValue("packingMethod", filteredOptions[0]);
|
||||
}
|
||||
}, [form, packerConfig]);
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader className="pb-1">
|
||||
<CardTitle className="text-md flex items-center gap-2">
|
||||
<PackageIcon className="h-5" />
|
||||
<span>{t("packerConfig.title")}</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{options.length > 0 ? (
|
||||
<FormProvider {...form}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="packingMethod"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>{t("packerMethod")}</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
className="grid grid-cols-2 md:grid-cols-3"
|
||||
>
|
||||
{options.map(({ name, value }) => (
|
||||
<FormItem
|
||||
key={value}
|
||||
className="flex items-center space-x-3 space-y-0"
|
||||
>
|
||||
<FormControl>
|
||||
<RadioGroupItem value={value} id={value} />
|
||||
</FormControl>
|
||||
<FormLabel className="text-xs" htmlFor={value}>
|
||||
{name}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</FormProvider>
|
||||
) : (
|
||||
<div className="flex items-center justify-center p-4 space-x-2">
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{t("loading")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ScrollTextIcon } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
export function QuickUsage() {
|
||||
const { t } = useTranslation(["common", "probeshell"]);
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-md flex items-center gap-2">
|
||||
<ScrollTextIcon className="h-5" />
|
||||
<span>{t("common:quickUsage.title")}</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ol className="list-decimal list-inside space-y-4 text-sm">
|
||||
<li>{t("probeshell:quickUsage.step1")}</li>
|
||||
<li>{t("probeshell:quickUsage.step2")}</li>
|
||||
<li>{t("probeshell:quickUsage.step3")}</li>
|
||||
</ol>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { QuickUsage } from "@/components/probeshell/quick-usage";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import type { ProbeShellResult } from "@/types/probeshell";
|
||||
import CodeViewer from "../code-viewer";
|
||||
import { MultiPackResult } from "../memshell/results/multi-packer";
|
||||
import { BasicInfo } from "./basic-info";
|
||||
|
||||
export default function ShellResult({
|
||||
packResult,
|
||||
allPackResults,
|
||||
packMethod,
|
||||
generateResult,
|
||||
}: Readonly<{
|
||||
packResult: string | undefined;
|
||||
allPackResults: Map<string, string> | undefined;
|
||||
packMethod: string;
|
||||
generateResult?: ProbeShellResult;
|
||||
}>) {
|
||||
const { t } = useTranslation();
|
||||
if (!generateResult) {
|
||||
return <QuickUsage />;
|
||||
}
|
||||
const showCode = packMethod === "JSP";
|
||||
const height = 550;
|
||||
return (
|
||||
<Tabs defaultValue="packResult">
|
||||
<TabsList className="grid w-full grid-cols-1">
|
||||
<TabsTrigger value="packResult">
|
||||
{t("common:generateResult")}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="packResult" className="my-2 space-y-4">
|
||||
<BasicInfo generateResult={generateResult} />
|
||||
{allPackResults && (
|
||||
<MultiPackResult
|
||||
allPackResults={allPackResults}
|
||||
shellClassName={generateResult?.shellClassName}
|
||||
packMethod={packMethod}
|
||||
height={height}
|
||||
/>
|
||||
)}
|
||||
{packResult && (
|
||||
<CodeViewer
|
||||
code={packResult}
|
||||
header={
|
||||
<div className="flex items-center justify-between text-xs gap-2">
|
||||
<span>
|
||||
{t("common:packerMethod")}:{packMethod}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
({packResult?.length})
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
wrapLongLines={!showCode}
|
||||
showLineNumbers={showCode}
|
||||
language={showCode ? "java" : "text"}
|
||||
height={height}
|
||||
/>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user