Files
MemShellParty/web/app/components/memshell/main-config-card.tsx
T
2026-02-08 20:44:00 +08:00

503 lines
18 KiB
TypeScript

import { ArrowUpRightIcon, InfoIcon, ServerIcon } from "lucide-react";
import { useCallback, useEffect, useMemo } from "react";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { AntSwordTabContent } from "@/components/memshell/tabs/antsword-tab";
import { BehinderTabContent } from "@/components/memshell/tabs/behinder-tab";
import { CommandTabContent } from "@/components/memshell/tabs/command-tab";
import CustomTabContent from "@/components/memshell/tabs/custom-tab";
import { GodzillaTabContent } from "@/components/memshell/tabs/godzilla-tab";
import { NeoRegTabContent } from "@/components/memshell/tabs/neoreg-tab";
import { Suo5TabContent } from "@/components/memshell/tabs/suo5-tab";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Tabs } from "@/components/ui/tabs";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
type MainConfig,
type ServerConfig,
ShellToolType,
} from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema";
import { Spinner } from "../ui/spinner";
import { JREVersionFormField } from "./jreversion-field";
import { ServerVersionFormField } from "./serverversion-field";
import { ProxyTabContent } from "./tabs/proxy-tab";
export default function MainConfigCard({
mainConfig,
form,
servers,
}: Readonly<{
mainConfig: MainConfig | undefined;
form: UseFormReturn<MemShellFormSchema>;
servers?: ServerConfig;
}>) {
const { t } = useTranslation(["common", "memshell"]);
const server = useWatch({
control: form.control,
name: "server",
});
const shellTool = useWatch({
control: form.control,
name: "shellTool",
});
const serverToolMap = useMemo(() => {
if (!mainConfig || !server) {
return undefined;
}
return mainConfig[server];
}, [mainConfig, server]);
const serverOptions = useMemo(() => Object.keys(servers ?? {}), [servers]);
const shellTools = useMemo(() => {
if (!serverToolMap) {
return [];
}
const tools = Object.keys(serverToolMap).map(
(tool) => tool as ShellToolType,
);
return Array.from(new Set([...tools, ShellToolType.Custom]));
}, [serverToolMap]);
const customShellTypes = useMemo(() => {
if (!server) {
return [];
}
return servers?.[server] ?? [];
}, [server, servers]);
const shellTypes = useMemo(() => {
if (!serverToolMap || !server) {
return [];
}
if (shellTool === ShellToolType.Custom) {
return customShellTypes;
}
return serverToolMap[shellTool] ?? [];
}, [customShellTypes, server, serverToolMap, shellTool]);
useEffect(() => {
if (!mainConfig || !server) {
return;
}
const toolMap = mainConfig[server];
if (!toolMap) {
return;
}
const toolKeys = Object.keys(toolMap);
if (toolKeys.length === 0) {
return;
}
const currentShellTool = form.getValues("shellTool") as ShellToolType;
const nextShellTool = toolMap[currentShellTool]
? currentShellTool
: (toolKeys[0] as ShellToolType);
if (nextShellTool !== currentShellTool) {
form.setValue("shellTool", nextShellTool);
}
if (nextShellTool !== ShellToolType.Custom) {
const nextShellTypes = toolMap[nextShellTool] ?? [];
if (nextShellTypes.length > 0) {
const currentShellType = form.getValues("shellType");
if (currentShellType !== nextShellTypes[0]) {
form.setValue("shellType", nextShellTypes[0]);
}
}
}
const currentTargetJdk = form.getValues("targetJdkVersion") as string;
const currentJdkVersion = Number.parseInt(currentTargetJdk, 10);
const shouldRaiseJdkVersion =
(server === "SpringWebFlux" || server === "XXLJOB") &&
currentJdkVersion <= 52;
const nextJdkVersion = shouldRaiseJdkVersion ? "52" : "50";
if (currentTargetJdk !== nextJdkVersion) {
form.setValue("targetJdkVersion", nextJdkVersion);
}
form.resetField("serverVersion");
form.resetField("byPassJavaModule");
form.resetField("urlPattern");
}, [form, mainConfig, server]);
useEffect(() => {
if (shellTool !== ShellToolType.Custom || customShellTypes.length === 0) {
return;
}
const currentShellType = form.getValues("shellType");
if (currentShellType !== customShellTypes[0]) {
form.setValue("shellType", customShellTypes[0]);
}
}, [customShellTypes, form, shellTool]);
const handleShellToolChange = useCallback(
(value: string) => {
const resetCommand = () => {
form.resetField("commandParamName");
form.resetField("implementationClass");
form.resetField("encryptor");
};
const resetGodzilla = () => {
form.resetField("godzillaKey");
form.resetField("godzillaPass");
form.resetField("headerName");
form.resetField("headerValue");
};
const resetBehinder = () => {
form.resetField("behinderPass");
form.resetField("headerName");
form.resetField("headerValue");
};
const resetSuo5 = () => {
form.resetField("headerName");
form.resetField("headerValue");
};
const resetAntSword = () => {
form.resetField("antSwordPass");
form.resetField("headerName");
form.resetField("headerValue");
};
const resetNeoreGeorg = () => {
form.setValue("headerName", "Referer");
form.resetField("headerValue");
};
const resetCustom = () => {
form.resetField("shellClassBase64");
};
if (serverToolMap) {
let currentShellTypes = null;
if (value === ShellToolType.Custom) {
currentShellTypes = customShellTypes;
} else {
currentShellTypes = serverToolMap[value];
}
// Set shellType directly instead of relying on useEffect.
if (currentShellTypes && currentShellTypes.length > 0) {
form.setValue("shellType", currentShellTypes[0]);
}
form.resetField("urlPattern");
form.resetField("shellClassName");
form.resetField("injectorClassName");
if (value === ShellToolType.Godzilla) {
resetGodzilla();
} else if (value === ShellToolType.Behinder) {
resetBehinder();
} else if (value === ShellToolType.Command) {
resetCommand();
} else if (value === ShellToolType.Suo5) {
resetSuo5();
} else if (value === ShellToolType.AntSword) {
resetAntSword();
} else if (value === ShellToolType.NeoreGeorg) {
resetNeoreGeorg();
} else if (value === ShellToolType.Custom) {
resetCustom();
}
}
form.setValue("shellTool", value);
},
[customShellTypes, form, serverToolMap],
);
return (
<>
<Card>
<CardHeader className="pb-1">
<CardTitle className="text-md flex items-center gap-2">
<ServerIcon className="h-5" />
<span>{t("common:mainConfig.title")}</span>
</CardTitle>
</CardHeader>
<CardContent>
{!mainConfig ? (
<div className="flex items-center justify-center p-4 gap-4 h-100">
<Spinner />
<span className="text-sm text-muted-foreground">
{t("loading")}
</span>
</div>
) : (
<div className="flex flex-col gap-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<Controller
control={form.control}
name="server"
render={({ field }) => (
<Field>
<FieldContent>
<FieldLabel htmlFor="server">
{t("common:server")}
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<SelectTrigger id="server">
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{serverOptions.map((serverOption) => (
<SelectItem
key={serverOption}
value={serverOption}
>
{serverOption}
</SelectItem>
))}
</SelectContent>
</Select>
<FieldDescription className="flex items-center">
{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("memshell:tips.targetServerRequest")}
<ArrowUpRightIcon className="h-4" />
</a>
</FieldDescription>
</FieldContent>
</Field>
)}
/>
<ServerVersionFormField form={form} />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
<Controller
control={form.control}
name="shellTool"
render={({ field }) => (
<Field>
<FieldContent>
<FieldLabel htmlFor="shellTool">
{t("common:shellTool")}
</FieldLabel>
<Select
value={field.value}
onValueChange={(v) =>
handleShellToolChange(v as string)
}
>
<SelectTrigger id="shellTool">
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger>
<SelectContent>
{shellTools.map((tool) => (
<SelectItem key={tool} value={tool}>
{tool}
</SelectItem>
))}
</SelectContent>
</Select>
</FieldContent>
</Field>
)}
/>
<JREVersionFormField form={form} />
</div>
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<Controller
control={form.control}
name="debug"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="debug"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="debug">{t("common:debug")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:debug.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="probe"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="probe"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="probe">{t("common:probe")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:probe.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="byPassJavaModule"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="bypass"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="bypass">
{t("common:byPassJavaModule")}
</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:byPassJavaModule.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="lambdaSuffix"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="lambdaSuffix"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="lambdaSuffix">
{t("common:lambdaSuffix")}
</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:lambdaSuffix.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="shrink"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="shrink">{t("common:shrink")}</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:shrink.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
<Controller
control={form.control}
name="staticInitialize"
render={({ field }) => (
<div className="flex items-center gap-2">
<Switch
id="staticInitialize"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="staticInitialize">
{t("common:staticInitialize")}
</Label>
<Tooltip>
<TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent>
<p>{t("common:staticInitialize.description")}</p>
</TooltipContent>
</Tooltip>
</div>
)}
/>
</div>
</div>
)}
</CardContent>
</Card>
{mainConfig && (
<Tabs value={shellTool} className="w-full">
<GodzillaTabContent form={form} shellTypes={shellTypes} />
<CommandTabContent form={form} shellTypes={shellTypes} />
<BehinderTabContent form={form} shellTypes={shellTypes} />
<AntSwordTabContent form={form} shellTypes={shellTypes} />
<Suo5TabContent tabValue="Suo5" form={form} shellTypes={shellTypes} />
<Suo5TabContent
tabValue="Suo5v2"
form={form}
shellTypes={shellTypes}
/>
<NeoRegTabContent form={form} shellTypes={shellTypes} />
<CustomTabContent form={form} shellTypes={shellTypes} />
<ProxyTabContent form={form} shellTypes={shellTypes} />
</Tabs>
)}
</>
);
}