feat: support i18n namespace

This commit is contained in:
ReaJason
2025-08-13 23:53:40 +08:00
parent a5f6411444
commit 3f5a2ddb6b
41 changed files with 987 additions and 663 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ export function LanguageSwitcher() {
const { i18n } = useTranslation();
const toggleLanguage = () => {
const newLang = i18n.language === "en" ? "zh" : "en";
const newLang = i18n.language === "en" ? "zh-CN" : "en";
i18n.changeLanguage(newLang);
};
-20
View File
@@ -1,20 +0,0 @@
import { InfoIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
export function JreTip() {
const { t } = useTranslation();
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<InfoIcon className="cursor-pointer h-4" />
</TooltipTrigger>
<TooltipContent>
<p>{t("tips.jreTip")}</p>
<p>{t("tips.jreTip2")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
@@ -92,7 +92,7 @@ export default function MainConfigCard({
]);
const [shellTypes, setShellTypes] = useState<string[]>([]);
const shellTool = form.watch("shellTool");
const { t } = useTranslation();
const { t } = useTranslation(["common", "memshell"]);
const [serverVersionOptions, setServerVersionOptions] = useState(
defaultServerVersionOptions,
@@ -137,7 +137,7 @@ export default function MainConfigCard({
) {
form.setValue("targetJdkVersion", "52");
} else {
form.resetField("targetJdkVersion");
form.setValue("targetJdkVersion", "50");
}
// 特殊的服务需要指定版本
@@ -259,7 +259,7 @@ export default function MainConfigCard({
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<ServerIcon className="h-5" />
<span>{t("configs.main-config")}</span>
<span>{t("common:mainConfig.title")}</span>
</CardTitle>
</CardHeader>
<CardContent>
@@ -269,7 +269,7 @@ export default function MainConfigCard({
name="server"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("mainConfig.server")}</FormFieldLabel>
<FormFieldLabel>{t("common:server")}</FormFieldLabel>
<Select
onValueChange={(v) => {
field.onChange(v);
@@ -279,7 +279,9 @@ export default function MainConfigCard({
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
<SelectValue
placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
@@ -291,14 +293,14 @@ export default function MainConfigCard({
</SelectContent>
</Select>
<FormDescription className="flex items-center">
{t("tips.targetServerNotFound")}&nbsp;
{t("memshell:tips.targetServerNotFound")}&nbsp;
<a
href="https://github.com/ReaJason/MemShellParty/issues/new?template=%E8%AF%B7%E6%B1%82%E9%80%82%E9%85%8D.md"
target="_blank"
rel="noreferrer"
className="flex items-center underline"
>
{t("tips.targetServerRequest")}
{t("memshell:tips.targetServerRequest")}
<ArrowUpRightIcon className="h-4" />
</a>
</FormDescription>
@@ -310,11 +312,13 @@ export default function MainConfigCard({
name="serverVersion"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("mainConfig.serverVersion")}</FormFieldLabel>
<FormFieldLabel>{t("common:serverVersion")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
<SelectValue
placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
@@ -337,9 +341,13 @@ export default function MainConfigCard({
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch id={debugId} checked={field.value} onCheckedChange={field.onChange} />
<Switch
id="debug"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel htmlFor={debugId}>{t("mainConfig.debug")}</FormLabel>
<FormLabel htmlFor="debug">{t("common:debug")}</FormLabel>
</FormItem>
)}
/>
@@ -349,9 +357,13 @@ export default function MainConfigCard({
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch id={bypassId} checked={field.value} onCheckedChange={field.onChange} />
<Switch
id="bypass"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor={bypassId}>{t("mainConfig.byPassJavaModule")}</Label>
<Label htmlFor="bypass">{t("common:byPassJavaModule")}</Label>
</FormItem>
)}
/>
@@ -361,9 +373,13 @@ export default function MainConfigCard({
render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0">
<FormControl>
<Switch id={shrinkId} checked={field.value} onCheckedChange={field.onChange} />
<Switch
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<Label htmlFor={shrinkId}>{t("mainConfig.shrink")}</Label>
<Label htmlFor="shrink">{t("common:shrink")}</Label>
</FormItem>
)}
/>
@@ -34,7 +34,7 @@ export default function PackageConfigCard({
const shellType = form.watch("shellType");
const server = form.watch("server");
const { t } = useTranslation();
const { t } = useTranslation("common");
useEffect(() => {
const filteredOptions = (packerConfig ?? []).filter((name) => {
@@ -52,7 +52,7 @@ export default function PackageConfigCard({
const mappedOptions = filteredOptions.map((name) => {
return {
name: t(`packageConfig.packer.${name}`),
name: t(name),
value: name,
};
});
@@ -72,7 +72,7 @@ export default function PackageConfigCard({
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<PackageIcon className="h-5" />
<span>{t("configs.package-config")}</span>
<span>{t("packerConfig.title")}</span>
</CardTitle>
</CardHeader>
<CardContent>
@@ -83,7 +83,7 @@ export default function PackageConfigCard({
name="packingMethod"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>{t("packageConfig.title")}</FormLabel>
<FormLabel>{t("packerMethod")}</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
+7 -7
View File
@@ -8,22 +8,22 @@ import {
} from "@/components/ui/card.tsx";
export function QuickUsage() {
const { t } = useTranslation();
const { t } = useTranslation(["common", "memshell"]);
return (
<Card>
<CardHeader>
<CardTitle className="text-md flex items-center gap-2">
<ScrollTextIcon className="h-5" />
<span>{t("quickUsage.title")}</span>
<span>{t("common:quickUsage.title")}</span>
</CardTitle>
</CardHeader>
<CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm">
<li>{t("quickUsage.step1")}</li>
<li>{t("quickUsage.step2")}</li>
<li>{t("quickUsage.step3")}</li>
<li>{t("quickUsage.step4")}</li>
<li>{t("quickUsage.step5")}</li>
<li>{t("memshell:quickUsage.step1")}</li>
<li>{t("memshell:quickUsage.step2")}</li>
<li>{t("memshell:quickUsage.step3")}</li>
<li>{t("memshell:quickUsage.step4")}</li>
<li>{t("memshell:quickUsage.step5")}</li>
</ol>
</CardContent>
</Card>
+26 -11
View File
@@ -10,7 +10,11 @@ export function AgentResult({
packMethod,
packResult,
generateResult,
}: Readonly<{ packMethod: string; packResult: string; generateResult?: MemShellResult }>) {
}: Readonly<{
packMethod: string;
packResult: string;
generateResult?: MemShellResult;
}>) {
const { t } = useTranslation();
const isPureAgent = packMethod === "AgentJar";
return (
@@ -18,14 +22,15 @@ export function AgentResult({
<CardHeader>
<CardTitle className="text-md flex items-center gap-2">
<ScrollTextIcon className="h-5" />
<span>{t("generateResult.usage")}</span>
<span>{t("common:usage")}</span>
</CardTitle>
</CardHeader>
<CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm">
<li className="flex items-center justify-between">
<span>
{t("download")} MemShellAgent.jar ({formatBytes(atob(packResult).length)})
{t("common:download")} MemShellAgent.jar (
{formatBytes(atob(packResult).length)})
</span>
<Button
size="sm"
@@ -40,28 +45,38 @@ export function AgentResult({
)
}
>
{t("download")}
{t("common:download")}
</Button>
</li>
{isPureAgent && (
<li className="flex items-center justify-between">
<span>{t("tips.download-jattach")}</span>
<span>{t("memshell:tips.download-jattach")}</span>
<Button
size="sm"
variant="outline"
className="w-28"
type="button"
onClick={() => window.open("https://github.com/jattach/jattach/releases")}
onClick={() =>
window.open("https://github.com/jattach/jattach/releases")
}
>
{t("download")}
{t("common:download")}
</Button>
</li>
)}
<Separator />
<li>{isPureAgent ? t("tips.agent-move-to-target") : t("tips.agent-move-to-target1")}</li>
<li>{t("tips.get-pid")}</li>
<li>{isPureAgent ? t("tips.execute-command") : t("tips.execute-command1")}</li>
<li>{t("tips.try-to-use-shell")}</li>
<li>
{isPureAgent
? t("memshell:tips.agent-move-to-target")
: t("memshell:tips.agent-move-to-target1")}
</li>
<li>{t("memshell:tips.get-pid")}</li>
<li>
{isPureAgent
? t("memshell:tips.execute-command")
: t("memshell:tips.execute-command1")}
</li>
<li>{t("memshell:tips.try-to-use-shell")}</li>
</ol>
</CardContent>
</Card>
@@ -16,24 +16,35 @@ import {
import { CopyableField } from "../../copyable-field";
import { FeedbackAlert } from "./feedback-alert";
export function BasicInfo({ generateResult }: Readonly<{ generateResult?: MemShellResult }>) {
const { t } = useTranslation();
export function BasicInfo({
generateResult,
}: Readonly<{ generateResult?: MemShellResult }>) {
const { t } = useTranslation(["memshell", "common"]);
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("generateResult.basicInfo")}</span>
<span>{t("common:basicInfo")}</span>
</div>
<FeedbackAlert />
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<CopyableField label={t("mainConfig.server")} text={generateResult?.shellConfig.server} />
<CopyableField label={t("mainConfig.shellTool")} text={generateResult?.shellConfig.shellTool} />
<CopyableField label={t("mainConfig.shellMountType")} text={generateResult?.shellConfig.shellType} />
<CopyableField
label={t("common:server")}
text={generateResult?.shellConfig.server}
/>
<CopyableField
label={t("mainConfig.shellTool")}
text={generateResult?.shellConfig.shellTool}
/>
<CopyableField
label={t("mainConfig.shellMountType")}
text={generateResult?.shellConfig.shellType}
/>
{!shouldHidden(generateResult?.shellConfig?.shellType) && (
<CopyableField
label={t("mainConfig.urlPattern")}
@@ -42,22 +53,33 @@ export function BasicInfo({ generateResult }: Readonly<{ generateResult?: MemShe
/>
)}
</div>
{generateResult?.shellConfig.shellTool !== ShellToolType.Custom && <Separator className="my-1" />}
{generateResult?.shellConfig.shellTool !== ShellToolType.Custom && (
<Separator className="my-1" />
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{generateResult?.shellConfig.shellTool === ShellToolType.Behinder && (
<>
<CopyableField label={t("shellToolConfig.behinderScriptType")} text="jsp" />
<CopyableField
label={t("shellToolConfig.behinderScriptType")}
text="jsp"
/>
<CopyableField
label={t("shellToolConfig.behinderEncryptType")}
text={t("shellToolConfig.behinderDefaultEncryptType")}
/>
<CopyableField
label={t("shellToolConfig.behinderPass")}
text={(generateResult?.shellToolConfig as BehinderShellToolConfig).pass}
value={(generateResult?.shellToolConfig as BehinderShellToolConfig).pass}
label={t("shellToolConfig.behinder.pass")}
text={
(generateResult?.shellToolConfig as BehinderShellToolConfig)
.pass
}
value={
(generateResult?.shellToolConfig as BehinderShellToolConfig)
.pass
}
/>
<CopyableField
label={t("shellToolConfig.customHeader")}
label={t("shellToolConfig.behinder.header")}
text={`${(generateResult?.shellToolConfig as BehinderShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as BehinderShellToolConfig).headerValue}`}
value={`${(generateResult?.shellToolConfig as BehinderShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as BehinderShellToolConfig).headerValue}`}
/>
@@ -66,18 +88,33 @@ export function BasicInfo({ generateResult }: Readonly<{ generateResult?: MemShe
{generateResult?.shellConfig.shellTool === ShellToolType.Godzilla && (
<>
<CopyableField
label={t("shellToolConfig.pass")}
text={(generateResult?.shellToolConfig as GodzillaShellToolConfig).pass}
value={(generateResult?.shellToolConfig as GodzillaShellToolConfig).pass}
label={t("shellToolConfig.godzilla.pass")}
text={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.pass
}
value={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.pass
}
/>
<CopyableField
label={t("shellToolConfig.key")}
text={(generateResult?.shellToolConfig as GodzillaShellToolConfig).key}
value={(generateResult?.shellToolConfig as GodzillaShellToolConfig).key}
label={t("shellToolConfig.godzilla.key")}
text={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.key
}
value={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.key
}
/>
<CopyableField label={t("shellToolConfig.godzillaEncryptor")} text="JAVA_AES_BASE64" />
<CopyableField
label={t("shellToolConfig.godzillaHeader")}
label={t("shellToolConfig.godzilla.encryptor")}
text="JAVA_AES_BASE64"
/>
<CopyableField
label={t("shellToolConfig.godzilla.header")}
text={`${(generateResult?.shellToolConfig as GodzillaShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as GodzillaShellToolConfig).headerValue}`}
value={`${(generateResult?.shellToolConfig as GodzillaShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as GodzillaShellToolConfig).headerValue}`}
/>
@@ -85,9 +122,15 @@ export function BasicInfo({ generateResult }: Readonly<{ generateResult?: MemShe
)}
{generateResult?.shellConfig.shellTool === ShellToolType.Command && (
<CopyableField
label={t("shellToolConfig.paramName")}
text={(generateResult?.shellToolConfig as CommandShellToolConfig).paramName}
value={(generateResult?.shellToolConfig as CommandShellToolConfig).paramName}
label={t("common:paramName")}
text={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
value={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
/>
)}
{generateResult?.shellConfig.shellTool === ShellToolType.Suo5 && (
@@ -100,9 +143,15 @@ export function BasicInfo({ generateResult }: Readonly<{ generateResult?: MemShe
{generateResult?.shellConfig.shellTool === ShellToolType.AntSword && (
<>
<CopyableField
label={t("shellToolConfig.antSwordPass")}
text={(generateResult?.shellToolConfig as AntSwordShellToolConfig).pass}
value={(generateResult?.shellToolConfig as AntSwordShellToolConfig).pass}
label={t("shellToolConfig.antSword.pass")}
text={
(generateResult?.shellToolConfig as AntSwordShellToolConfig)
.pass
}
value={
(generateResult?.shellToolConfig as AntSwordShellToolConfig)
.pass
}
/>
<CopyableField
label={t("shellToolConfig.httpHeader")}
@@ -111,9 +160,14 @@ export function BasicInfo({ generateResult }: Readonly<{ generateResult?: MemShe
/>
</>
)}
{generateResult?.shellConfig.shellTool === ShellToolType.NeoreGeorg && (
{generateResult?.shellConfig.shellTool ===
ShellToolType.NeoreGeorg && (
<>
<CopyableField label={t("shellToolConfig.neoreGeorgKey")} text="key" value="key" />
<CopyableField
label={t("shellToolConfig.neoreGeorgKey")}
text="key"
value="key"
/>
<CopyableField
label={t("shellToolConfig.neoreGeorgHeader")}
text={`${(generateResult?.shellToolConfig as NeoreGeorgShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as NeoreGeorgShellToolConfig).headerValue}`}
@@ -14,7 +14,7 @@ import {
import { Button } from "@/components/ui/button";
export function FeedbackAlert() {
const { t } = useTranslation();
const { t } = useTranslation("memshell");
return (
<AlertDialog>
<AlertDialogTrigger asChild>
@@ -33,7 +33,7 @@ export function FeedbackAlert() {
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
<AlertDialogCancel>{t("common:cancel")}</AlertDialogCancel>
<AlertDialogAction
onClick={() =>
window.open(
@@ -41,7 +41,7 @@ export function FeedbackAlert() {
)
}
>
{t("feedback")}
{t("common:feedback")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
@@ -20,7 +20,7 @@ export function JarResult({
)
}
>
{t("download")} Jar
{t("common:download")} Jar
</Button>
</div>
);
@@ -17,16 +17,20 @@ export function MultiPackResult({
packMethod: string;
}>) {
const showCode = packMethod === "JSP";
const {t} = useTranslation();
const { t } = useTranslation();
const packMethods = Object.keys(allPackResults ?? {});
const [selectedMethod, setSelectedMethod] = useState(packMethods[0]);
const [packResult, setPackResult] = useState(allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "");
const [packResult, setPackResult] = useState(
allPackResults?.[selectedMethod as keyof typeof allPackResults] ?? "",
);
useEffect(() => {
const methods = Object.keys(allPackResults ?? {});
const firstMethod = methods[0];
setSelectedMethod(firstMethod);
setPackResult(allPackResults?.[firstMethod as keyof typeof allPackResults] ?? "");
setPackResult(
allPackResults?.[firstMethod as keyof typeof allPackResults] ?? "",
);
}, [allPackResults]);
return (
@@ -44,7 +48,9 @@ export function MultiPackResult({
value={selectedMethod}
>
<SelectTrigger className="h-7 text-xs [&_svg]:h-4 [&_svg]:w-4">
<span className="text-muted-foreground">{t("packageConfig.title")}:&nbsp;</span>
<span className="text-muted-foreground">
{t("common:packerMethod")}:&nbsp;
</span>
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -53,9 +53,11 @@ export function ResultComponent({
header={
<div className="flex items-center justify-between text-xs gap-2">
<span>
{t("packageConfig.title")}{packMethod}
{t("common:packerMethod")}{packMethod}
</span>
<span className="text-muted-foreground">
({packResult?.length})
</span>
<span className="text-muted-foreground">({packResult?.length})</span>
</div>
}
wrapLongLines={!showCode}
+26 -10
View File
@@ -27,16 +27,20 @@ export default function ShellResult({
packMethod: string;
generateResult?: MemShellResult;
}>) {
const { t } = useTranslation();
const { t } = useTranslation(["common", "memshell"]);
if (!generateResult) {
return <QuickUsage />;
}
return (
<Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="packResult">{t("generateResult.title1")}</TabsTrigger>
<TabsTrigger value="shell">{t("generateResult.title2")}</TabsTrigger>
<TabsTrigger value="injector">{t("generateResult.title3")}</TabsTrigger>
<TabsTrigger value="packResult">
{t("common:generateResult")}
</TabsTrigger>
<TabsTrigger value="shell">{t("memshell:shellClass")}</TabsTrigger>
<TabsTrigger value="injector">
{t("memshell:injectorClass")}
</TabsTrigger>
</TabsList>
<TabsContent value="packResult" className="my-2 space-y-4">
<BasicInfo generateResult={generateResult} />
@@ -50,7 +54,11 @@ export default function ShellResult({
<TabsContent value="shell" className="mt-4">
<CodeViewer
showLineNumbers={false}
header={<div className="text-xs truncate">{generateResult?.shellClassName}</div>}
header={
<div className="text-xs truncate">
{generateResult?.shellClassName}
</div>
}
button={
<Button
variant="ghost"
@@ -59,10 +67,13 @@ export default function ShellResult({
className="h-7 w-7 [&_svg]:h-4 [&_svg]:w-4"
onClick={() => {
if (!generateResult?.shellBytesBase64Str) {
toast.warning(t("tips.shellBytesEmpty"));
toast.warning(t("memshell:tips.shellBytesEmpty"));
return;
}
downloadBytes(generateResult?.shellBytesBase64Str, generateResult?.shellClassName);
downloadBytes(
generateResult?.shellBytesBase64Str,
generateResult?.shellClassName,
);
}}
>
<DownloadIcon className="h-4 w-4" />
@@ -78,7 +89,9 @@ export default function ShellResult({
<CodeViewer
showLineNumbers={false}
wrapLongLines={true}
header={<div className="text-xs">{generateResult?.injectorClassName}</div>}
header={
<div className="text-xs">{generateResult?.injectorClassName}</div>
}
button={
<Button
variant="ghost"
@@ -87,10 +100,13 @@ export default function ShellResult({
className="h-7 w-7 [&_svg]:h-4 [&_svg]:w-4"
onClick={() => {
if (!generateResult?.injectorBytesBase64Str) {
toast.warning(t("tips.shellBytesEmpty"));
toast.warning(t("memshell:tips.shellBytesEmpty"));
return;
}
downloadBytes(generateResult?.injectorBytesBase64Str, generateResult?.injectorClassName);
downloadBytes(
generateResult?.injectorBytesBase64Str,
generateResult?.injectorClassName,
);
}}
>
<DownloadIcon className="h-4 w-4" />
@@ -17,8 +17,11 @@ import { UrlPatternFormField } from "./urlpattern-field";
export function AntSwordTabContent({
form,
shellTypes,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema>; shellTypes: Array<string> }>) {
const { t } = useTranslation();
}: Readonly<{
form: UseFormReturn<MemShellFormSchema>;
shellTypes: Array<string>;
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="AntSword">
@@ -34,9 +37,12 @@ export function AntSwordTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.antSwordPass")} {t("optional")}
{t("shellToolConfig.antSword.pass")} {t("common:optional")}
</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -46,9 +52,12 @@ export function AntSwordTabContent({
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.headerName")}</FormFieldLabel>
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<FormControl>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormControl>
</FormFieldItem>
)}
@@ -59,9 +68,12 @@ export function AntSwordTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.headerValue")} {t("optional")}
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -12,8 +12,11 @@ import { UrlPatternFormField } from "./urlpattern-field";
export function BehinderTabContent({
form,
shellTypes,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema>; shellTypes: Array<string> }>) {
const { t } = useTranslation();
}: Readonly<{
form: UseFormReturn<MemShellFormSchema>;
shellTypes: Array<string>;
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="Behinder">
@@ -29,9 +32,12 @@ export function BehinderTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.behinderPass")} {t("optional")}
{t("shellToolConfig.behinder.pass")} {t("common:optional")}
</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -41,8 +47,11 @@ export function BehinderTabContent({
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.headerName")}</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -52,9 +61,12 @@ export function BehinderTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.headerValue")} {t("optional")}
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -11,11 +11,11 @@ import {
import { Input } from "@/components/ui/input.tsx";
import type { MemShellFormSchema } from "@/types/schema.ts";
export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<MemShellFormSchema> }>) {
const { t } = useTranslation();
export function OptionalClassFormField({
form,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema> }>) {
const { t } = useTranslation(["memshell", "common"]);
const [showAdvanced, setShowAdvanced] = useState(false);
const shellClassNameId = useId();
const injectClassNameId = useId();
return (
<Fragment>
<div className="pt-2">
@@ -28,7 +28,11 @@ export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<
>
<Settings className="h-4 w-4" />
{t("classNameOptions")}
{showAdvanced ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
{showAdvanced ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
</div>
{showAdvanced && (
@@ -38,10 +42,14 @@ export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<
name="shellClassName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel htmlFor={shellClassNameId}>
{t("mainConfig.shellClassName")} {t("optional")}
<FormFieldLabel htmlFor="shellClassName">
{t("mainConfig.shellClassName")} {t("common:optional")}
</FormFieldLabel>
<Input id={shellClassNameId} {...field} placeholder={t("placeholders.input")} />
<Input
id="shellClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -50,10 +58,14 @@ export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<
name="injectorClassName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel htmlFor={injectClassNameId}>
{t("mainConfig.injectorClassName")} {t("optional")}
<FormFieldLabel htmlFor="injectClassName">
{t("mainConfig.injectorClassName")} {t("common:optional")}
</FormFieldLabel>
<Input id={injectClassNameId} {...field} placeholder={t("placeholders.input")} />
<Input
id="injectClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -26,9 +26,15 @@ import { UrlPatternFormField } from "./urlpattern-field";
export function CommandTabContent({
form,
shellTypes,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema>; shellTypes: Array<string> }>) {
const { t } = useTranslation();
const { data } = useQuery<{ encryptors: Array<string>; implementationClasses: Array<string> }>({
}: Readonly<{
form: UseFormReturn<MemShellFormSchema>;
shellTypes: Array<string>;
}>) {
const { t } = useTranslation(["memshell", "common"]);
const { data } = useQuery<{
encryptors: Array<string>;
implementationClasses: Array<string>;
}>({
queryKey: ["commandConfigs"],
queryFn: async () => {
const response = await fetch(`${env.API_URL}/config/command/configs`);
@@ -51,10 +57,13 @@ export function CommandTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.paramName")} {t("optional")}
{t("common:paramName")} {t("common:optional")}
</FormFieldLabel>
<FormControl>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormControl>
</FormFieldItem>
)}
@@ -65,11 +74,17 @@ export function CommandTabContent({
name="encryptor"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.encryptor")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value} defaultValue="RAW">
<FormFieldLabel>{t("common:encryptor")}</FormFieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
defaultValue="RAW"
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
<SelectValue
placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
@@ -88,11 +103,19 @@ export function CommandTabContent({
name="implementationClass"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.implementationClass")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value} defaultValue="RuntimeExec">
<FormFieldLabel>
{t("common:implementationClass")}
</FormFieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
defaultValue="RuntimeExec"
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
<SelectValue
placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
</FormControl>
<SelectContent>
+20 -11
View File
@@ -22,10 +22,12 @@ import { UrlPatternFormField } from "./urlpattern-field";
export default function CustomTabContent({
form,
shellTypes,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema>; shellTypes: Array<string> }>) {
}: Readonly<{
form: UseFormReturn<MemShellFormSchema>;
shellTypes: Array<string>;
}>) {
const [isFile, setIsFile] = useState(false);
const optionOneId = useId();
const optionTwoId = useId();
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="Custom">
@@ -40,7 +42,7 @@ export default function CustomTabContent({
name="shellClassBase64"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.base64String")}</FormFieldLabel>
<FormFieldLabel>{t("shellClass")}</FormFieldLabel>
<RadioGroup
value={isFile ? "file" : "base64"}
onValueChange={(value) => {
@@ -50,12 +52,12 @@ export default function CustomTabContent({
className="flex items-center space-x-2"
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="base64" id={optionOneId} />
<Label htmlFor={optionOneId}>Base64</Label>
<RadioGroupItem value="base64" id="optionOne" />
<Label htmlFor="optionOne">Base64</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="file" id={optionTwoId} />
<Label htmlFor={optionTwoId}>File</Label>
<RadioGroupItem value="file" id="optionTwo" />
<Label htmlFor="optionTwo">File</Label>
</div>
</RadioGroup>
<FormControl className="mt-2">
@@ -66,18 +68,25 @@ export default function CustomTabContent({
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
const base64String = (event.target?.result as string)?.split(",")[1] || "";
const base64String =
(event.target?.result as string)?.split(
",",
)[1] || "";
field.onChange(base64String);
};
reader.readAsDataURL(file);
}
}}
accept=".class"
placeholder={t("placeholders.input")}
placeholder={t("common:placeholders.input")}
type="file"
/>
) : (
<Textarea {...field} placeholder={t("placeholders.input")} className="h-24" />
<Textarea
{...field}
placeholder={t("common:placeholders.input")}
className="h-24"
/>
)}
</FormControl>
<FormMessage />
@@ -12,8 +12,11 @@ import { UrlPatternFormField } from "./urlpattern-field";
export function GodzillaTabContent({
form,
shellTypes,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema>; shellTypes: Array<string> }>) {
const { t } = useTranslation();
}: Readonly<{
form: UseFormReturn<MemShellFormSchema>;
shellTypes: Array<string>;
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="Godzilla">
@@ -30,9 +33,13 @@ export function GodzillaTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.pass")} {t("optional")}
{t("shellToolConfig.godzilla.pass")}{" "}
{t("common:optional")}
</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -42,9 +49,12 @@ export function GodzillaTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.key")} {t("optional")}
{t("shellToolConfig.godzilla.key")} {t("common:optional")}
</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -53,8 +63,11 @@ export function GodzillaTabContent({
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.headerName")}</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -64,9 +77,12 @@ export function GodzillaTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.headerValue")} {t("optional")}
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -12,8 +12,11 @@ import { UrlPatternFormField } from "./urlpattern-field";
export function NeoRegTabContent({
form,
shellTypes,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema>; shellTypes: Array<string> }>) {
const { t } = useTranslation();
}: Readonly<{
form: UseFormReturn<MemShellFormSchema>;
shellTypes: Array<string>;
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="NeoreGeorg">
@@ -29,8 +32,11 @@ export function NeoRegTabContent({
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.headerName")}</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -40,9 +46,12 @@ export function NeoRegTabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.headerValue")} {t("optional")}
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -18,8 +18,11 @@ import type { MemShellFormSchema } from "@/types/schema.ts";
export function ShellTypeFormField({
form,
shellTypes,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema>; shellTypes: Array<string> }>) {
const { t } = useTranslation();
}: Readonly<{
form: UseFormReturn<MemShellFormSchema>;
shellTypes: Array<string>;
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<FormField
@@ -37,7 +40,7 @@ export function ShellTypeFormField({
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t("placeholders.select")} />
<SelectValue placeholder={t("common:placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent key={shellTypes?.join(",")}>
@@ -48,7 +51,9 @@ export function ShellTypeFormField({
</SelectItem>
))
) : (
<SelectItem value=" ">{t("tips.shellToolNotSelected")}</SelectItem>
<SelectItem value=" ">
{t("tips.shellToolNotSelected")}
</SelectItem>
)}
</SelectContent>
</Select>
+15 -6
View File
@@ -12,8 +12,11 @@ import { UrlPatternFormField } from "./urlpattern-field";
export function Suo5TabContent({
form,
shellTypes,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema>; shellTypes: Array<string> }>) {
const { t } = useTranslation();
}: Readonly<{
form: UseFormReturn<MemShellFormSchema>;
shellTypes: Array<string>;
}>) {
const { t } = useTranslation(["memshell", "common"]);
return (
<FormProvider {...form}>
<TabsContent value="Suo5">
@@ -29,8 +32,11 @@ export function Suo5TabContent({
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.headerName")}</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<FormFieldLabel>{t("common:headerName")}</FormFieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -40,9 +46,12 @@ export function Suo5TabContent({
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>
{t("shellToolConfig.headerValue")} {t("optional")}
{t("common:headerValue")} {t("common:optional")}
</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -10,8 +10,10 @@ import { Input } from "@/components/ui/input.tsx";
import { shouldHidden } from "@/lib/utils";
import type { MemShellFormSchema } from "@/types/schema.ts";
export function UrlPatternFormField({ form }: Readonly<{ form: UseFormReturn<MemShellFormSchema> }>) {
const { t } = useTranslation();
export function UrlPatternFormField({
form,
}: Readonly<{ form: UseFormReturn<MemShellFormSchema> }>) {
const { t } = useTranslation("common");
const shellType = form.watch("shellType");
return (
<FormProvider {...form}>
@@ -19,8 +21,10 @@ export function UrlPatternFormField({ form }: Readonly<{ form: UseFormReturn<Mem
control={form.control}
name="urlPattern"
render={({ field }) => (
<FormFieldItem className={shouldHidden(shellType) ? "hidden" : "grid"}>
<FormFieldLabel>{t("mainConfig.urlPattern")}</FormFieldLabel>
<FormFieldItem
className={shouldHidden(shellType) ? "hidden" : "grid"}
>
<FormFieldLabel>{t("urlPattern")}</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} />
<FormMessage />
</FormFieldItem>
+5 -3
View File
@@ -5,7 +5,9 @@ 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 }>) {
export function BasicInfo({
generateResult,
}: Readonly<{ generateResult?: ProbeShellResult }>) {
const { t } = useTranslation();
return (
<Card>
@@ -13,7 +15,7 @@ export function BasicInfo({ generateResult }: Readonly<{ generateResult?: ProbeS
<CardTitle className="flex items-center justify-between">
<div className="text-md flex items-center gap-2">
<FileTextIcon className="h-5" />
<span>{t("generateResult.basicInfo")}</span>
<span>{t("common:basicInfo")}</span>
</div>
<FeedbackAlert />
</CardTitle>
@@ -21,7 +23,7 @@ export function BasicInfo({ generateResult }: Readonly<{ generateResult?: ProbeS
<CardContent>
<div className="grid grid-cols-1 gap-2">
<CopyableField
label={t("mainConfig.shellClassName")}
label={t("probeshell:shellClassName")}
value={generateResult?.shellClassName}
text={`${generateResult?.shellClassName} (${generateResult?.shellSize} bytes)`}
/>
+178 -138
View File
@@ -14,18 +14,23 @@ import {
} 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 {
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: "中间件类型" },
{ value: "JDK" as const, label: "JDK 信息" },
{ value: "Command" as const, label: "命令执行" },
{ value: "Bytecode" as const, label: "自定义字节码执行" },
{ value: "Server" as const, label: "server" },
{ value: "JDK" as const, label: "jdk" },
{ value: "Command" as const, label: "command" },
{ value: "Bytecode" as const, label: "bytecode" },
] as const;
const MIDDLEWARE_OPTIONS = [
@@ -45,12 +50,11 @@ const MIDDLEWARE_OPTIONS = [
] as const;
const PROBE_METHOD_OPTIONS = [
{ value: "Sleep", label: "Sleep 延迟探测" },
{ value: "Sleep", label: "Sleep" },
{ value: "DNSLog", label: "DNSLog" },
{ value: "ResponseBody", label: "ResponseBody" },
] as const;
// 默认值配置
const DEFAULT_FORM_VALUES = {
reqParamName: "payload",
reqHeaderName: "X-PAYLOAD",
@@ -64,7 +68,7 @@ interface MainConfigCardProps {
}
export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
const { t } = useTranslation();
const { t } = useTranslation(["common", "probeshell"]);
const watchedProbeMethod = form.watch("probeMethod");
const watchedProbeContent = form.watch("probeContent");
@@ -75,12 +79,13 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
Sleep: ["Server"],
} as const;
const allowedValues = filterMap[watchedProbeMethod as keyof typeof filterMap];
const allowedValues =
filterMap[watchedProbeMethod as keyof typeof filterMap];
if (!allowedValues) return PROBE_OPTIONS;
return PROBE_OPTIONS.filter(opt =>
allowedValues.includes(opt.value as never)
return PROBE_OPTIONS.filter((opt) =>
allowedValues.includes(opt.value as never),
);
}, [watchedProbeMethod]);
@@ -100,64 +105,70 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
resetFormValues();
}, [resetFormValues]);
const ContentOptionsSelect = useMemo(() => (
<FormField
control={form.control}
name="probeContent"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel></FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value || ""}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择要探测的内容..." />
</SelectTrigger>
</FormControl>
<SelectContent>
{filteredOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
), [form.control, filteredOptions]);
const RequestParamField = useMemo(() => (
<div className="space-y-4 pt-4 border-t mt-4">
const ContentOptionsSelect = useMemo(
() => (
<FormField
control={form.control}
name="reqParamName"
name="probeContent"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>Request Param Name</FormFieldLabel>
<FormControl>
<Input placeholder="例如: cmd, data, ..." {...field} />
</FormControl>
<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>
)}
/>
</div>
), [form.control]);
),
[form.control, filteredOptions, t],
);
const SleepFields = useMemo(() => (
<div className="space-y-4 pt-4 border-t mt-4">
<div className="space-y-4">
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></FormFieldLabel>
<FormFieldLabel>{t("probeshell:sleepServer")}</FormFieldLabel>
<Select onValueChange={field.onChange} value={field.value || ""}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="选择中间件..." />
<SelectValue placeholder={t("placeholders.select")} />
</SelectTrigger>
</FormControl>
<SelectContent>
@@ -177,11 +188,11 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
name="seconds"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel> ()</FormFieldLabel>
<FormFieldLabel>{t("probeshell:sleepSeconds")}</FormFieldLabel>
<FormControl>
<Input
type="number"
placeholder="例如: 5"
placeholder={t("placeholders.input")}
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
@@ -191,12 +202,14 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
)}
/>
</div>
</div>
), [form.control]);
),
[form.control, t],
);
const renderDynamicFields = useCallback(() => {
const isBodyMethod = watchedProbeMethod === "ResponseBody";
const isCommandOrBytecode = watchedProbeContent === "Command" || watchedProbeContent === "Bytecode";
const isCommandOrBytecode =
watchedProbeContent === "Command" || watchedProbeContent === "Bytecode";
const isSleepMethod = watchedProbeMethod === "Sleep";
const isServerContent = watchedProbeContent === "Server";
@@ -211,90 +224,110 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
return null;
}, [watchedProbeMethod, watchedProbeContent, RequestParamField, SleepFields]);
const DNSLogSection = useMemo(() => (
const DNSLogSection = useMemo(
() => (
<FormField
control={form.control}
name="host"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>DNSLog </FormFieldLabel>
<FormFieldLabel>{t("probeshell:dnslog.host")}</FormFieldLabel>
<FormControl>
<Input placeholder="例如: abcde.DNSLog.cn" {...field} />
<Input placeholder={t("placeholders.input")} {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
), [form.control]);
),
[form.control, t],
);
const MiddlewareSelect = useMemo(() => (
<FormField
control={form.control}
name="server"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel></FormFieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="选择中间件..." />
</SelectTrigger>
</FormControl>
<SelectContent>
{Object.keys(servers ?? {}).map((server: string) => (
<SelectItem key={server} value={server}>
{server}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
), [form.control, servers]);
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 ?? {}).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("mainConfig.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("mainConfig.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("mainConfig.shrink")}</Label>
</FormItem>
)}
/>
</div>
), [form.control, 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>
)}
/>
</div>
),
[form.control, t],
);
return (
<FormProvider {...form}>
@@ -302,17 +335,20 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<ServerIcon className="h-5 w-5" />
<span>{t("configs.main-config")}</span>
<span>{t("mainConfig.title")}</span>
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<CardContent className="space-y-2">
<FormField
control={form.control}
name="probeMethod"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel></FormFieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormFieldLabel>{t("probeshell:probeMethod")}</FormFieldLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue />
@@ -343,9 +379,13 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel htmlFor="shellClassName">
{t("mainConfig.shellClassName")} {t("optional")}
{t("probeshell:shellClassName")} {t("optional")}
</FormFieldLabel>
<Input id="shellClassName" {...field} placeholder={t("placeholders.input")} />
<Input
id="shellClassName"
{...field}
placeholder={t("placeholders.input")}
/>
</FormFieldItem>
)}
/>
@@ -353,4 +393,4 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
</Card>
</FormProvider>
);
}
}
@@ -2,8 +2,18 @@ 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.tsx";
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card.tsx";
import {
FormControl,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/form.tsx";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx";
import type { PackerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
@@ -21,7 +31,7 @@ export default function PackageConfigCard({
form: UseFormReturn<ProbeShellFormSchema>;
}>) {
const [options, setOptions] = useState<Array<Option>>([]);
const { t } = useTranslation();
const { t } = useTranslation("common");
useEffect(() => {
const filteredOptions = (packerConfig ?? []).filter((name) => {
@@ -30,24 +40,27 @@ export default function PackageConfigCard({
const mappedOptions = filteredOptions.map((name) => {
return {
name: t(`packageConfig.packer.${name}`),
name: name,
value: name,
};
});
setOptions(mappedOptions);
const currentValue = form.getValues("packingMethod");
if (filteredOptions.length > 0 && (!currentValue || !filteredOptions.includes(currentValue))) {
if (
filteredOptions.length > 0 &&
(!currentValue || !filteredOptions.includes(currentValue))
) {
form.setValue("packingMethod", filteredOptions[0]);
}
}, [form, packerConfig, t]);
}, [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("configs.package-config")}</span>
<span>{t("packerConfig.title")}</span>
</CardTitle>
</CardHeader>
<CardContent>
@@ -58,7 +71,7 @@ export default function PackageConfigCard({
name="packingMethod"
render={({ field }) => (
<FormItem className="space-y-3">
<FormLabel>{t("packageConfig.title")}</FormLabel>
<FormLabel>{t("packerMethod")}</FormLabel>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
@@ -66,7 +79,10 @@ export default function PackageConfigCard({
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">
<FormItem
key={value}
className="flex items-center space-x-3 space-y-0"
>
<FormControl>
<RadioGroupItem value={value} id={value} />
</FormControl>
@@ -84,7 +100,9 @@ export default function PackageConfigCard({
) : (
<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>
<span className="text-sm text-muted-foreground">
{t("loading")}
</span>
</div>
)}
</CardContent>
+11 -8
View File
@@ -1,24 +1,27 @@
import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card.tsx";
export function QuickUsage() {
const { t } = useTranslation();
const { t } = useTranslation(["common", "probeshell"]);
return (
<Card>
<CardHeader>
<CardTitle className="text-md flex items-center gap-2">
<ScrollTextIcon className="h-5" />
<span>{t("quickUsage.title")}</span>
<span>{t("common:quickUsage.title")}</span>
</CardTitle>
</CardHeader>
<CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm">
<li>{t("quickUsage.step1")}</li>
<li>{t("quickUsage.step2")}</li>
<li>{t("quickUsage.step3")}</li>
<li>{t("quickUsage.step4")}</li>
<li>{t("quickUsage.step5")}</li>
<li>{t("probeshell:quickUsage.step1")}</li>
<li>{t("probeshell:quickUsage.step2")}</li>
<li>{t("probeshell:quickUsage.step3")}</li>
</ol>
</CardContent>
</Card>
+34 -24
View File
@@ -1,6 +1,11 @@
import { useTranslation } from "react-i18next";
import { QuickUsage } from "@/components/probeshell/quick-usage";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs.tsx";
import type { ProbeShellResult } from "@/types/probeshell";
import CodeViewer from "../code-viewer";
import { MultiPackResult } from "../memshell/results/multi-packer";
@@ -25,32 +30,37 @@ export default function ShellResult({
return (
<Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-1">
<TabsTrigger value="packResult">{t("generateResult.title1")}</TabsTrigger>
<TabsTrigger value="packResult">
{t("common:generateResult")}
</TabsTrigger>
</TabsList>
<TabsContent value="packResult" className="my-2 space-y-4">
<BasicInfo generateResult={generateResult} />
{
allPackResults && <MultiPackResult allPackResults={allPackResults} packMethod={packMethod} />
}
{
packResult && (
<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}
/>
)
}
{allPackResults && (
<MultiPackResult
allPackResults={allPackResults}
packMethod={packMethod}
/>
)}
{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={350}
/>
)}
</TabsContent>
</Tabs>
);