feat: support random param and default config (#50)

This commit is contained in:
ReaJason
2025-04-06 13:55:17 +08:00
parent 85e9eb8c70
commit 6b47b8e488
35 changed files with 251 additions and 116 deletions
+2 -3
View File
@@ -1,13 +1,12 @@
import { LanguageSwitcher } from "@/components/language-switcher";
import { ModeToggle } from "@/components/mode-toggle.tsx";
import { ThemeProvider } from "@/components/theme-provider.tsx";
import { Button } from "@/components/ui/button";
import { Toaster } from "@/components/ui/sonner";
import VersionBadge from "@/components/version-badge";
import { GitHubIcon } from "@/icon";
import { Button } from "@/components/ui/button";
import { Outlet } from "react-router-dom";
export default function RootLayout() {
return (
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
@@ -39,4 +38,4 @@ export default function RootLayout() {
</div>
</ThemeProvider>
);
}
}
+37 -13
View File
@@ -19,7 +19,7 @@ import {
WaypointsIcon,
ZapIcon,
} from "lucide-react";
import { JSX, useState } from "react";
import { JSX, useEffect, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { AntSwordTabContent } from "./tools/antsword-tab";
@@ -65,20 +65,45 @@ export function MainConfigCard({
const shellTool = form.watch("shellTool");
const { t } = useTranslation();
// 处理一下默认值 server 不刷新 shellType 的问题
useEffect(() => {
if (mainConfig) {
const initialServer = form.getValues("server");
if (initialServer && mainConfig[initialServer]) {
handleServerChange(initialServer);
}
}
}, [mainConfig, form]);
// 处理一下 shellTypes 由于 server 或 shellTool 变更时无法正常为 form.shellType 赋值的问题
useEffect(() => {
if (shellTypes.length > 0) {
form.setValue("shellType", shellTypes[0]);
}
}, [shellTypes, form]);
const handleServerChange = (value: string) => {
if (mainConfig) {
const newShellToolMap = mainConfig[value];
setShellToolMap(newShellToolMap);
const newShellTools = Object.keys(newShellToolMap);
setShellTools([...newShellTools.map((tool) => tool as ShellToolType), ShellToolType.Custom]);
if (newShellTools.length > 0) {
const firstTool = newShellTools[0];
setShellTypes(newShellToolMap[firstTool]);
form.setValue("shellTool", firstTool);
} else {
setShellTypes([]);
}
const currentShellTool = form.getValues("shellTool");
const firstTool = newShellTools[0];
let currentShellTypes = null;
if (!newShellToolMap[currentShellTool]) {
form.setValue("shellTool", firstTool);
currentShellTypes = newShellToolMap[firstTool];
} else {
currentShellTypes = newShellToolMap[currentShellTool];
}
setShellTypes(currentShellTypes);
// 特殊环境的 JDK 版本
if (
(value === "SpringWebFlux" || value === "XXLJOB") &&
Number.parseInt(form.getValues("targetJdkVersion") as string) < 52
@@ -88,8 +113,6 @@ export function MainConfigCard({
form.resetField("targetJdkVersion");
}
form.resetField("bypassJavaModule");
form.resetField("shellTool");
form.resetField("shellType");
form.resetField("urlPattern");
}
};
@@ -133,14 +156,15 @@ export function MainConfigCard({
};
if (shellToolMap) {
let currentShellTypes = null;
if (value === ShellToolType.Custom) {
setShellTypes(servers?.[form.getValues("server")] as string[]);
currentShellTypes = servers?.[form.getValues("server")] as string[];
} else {
setShellTypes(shellToolMap[value]);
currentShellTypes = shellToolMap[value];
}
setShellTypes(currentShellTypes);
form.resetField("urlPattern");
form.resetField("shellType");
form.resetField("shellClassName");
form.resetField("injectorClassName");
if (value === ShellToolType.Godzilla) {
+13 -11
View File
@@ -16,10 +16,10 @@ type Option = {
export function PackageConfigCard({
packerConfig,
form,
}: {
}: Readonly<{
packerConfig: PackerConfig | undefined;
form: UseFormReturn<FormSchema>;
}) {
}>) {
const [options, setOptions] = useState<Array<Option>>([]);
const shellType = form.watch("shellType");
@@ -39,15 +39,17 @@ export function PackageConfigCard({
}
return !name.startsWith("Agent") && !name.toLowerCase().startsWith("xxl");
});
setOptions(
filteredOptions.map((name) => {
return {
name: t(`packageConfig.packer.${name}`),
value: name,
};
}),
);
if (filteredOptions.length > 0) {
const mappedOptions = filteredOptions.map((name) => {
return {
name: t(`packageConfig.packer.${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, server, shellType, t]);
+6 -2
View File
@@ -28,7 +28,9 @@ export function AntSwordTabContent({
name="antSwordPass"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.antSwordPass")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.antSwordPass")} {t("optional")}
</FormLabel>
<Input {...field} placeholder={t("placeholders.input")} className="h-8" />
</FormItem>
)}
@@ -49,7 +51,9 @@ export function AntSwordTabContent({
name="headerValue"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.headerValue")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.headerValue")} {t("optional")}
</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.headerValue")} className="h-8" />
</FormItem>
)}
+6 -2
View File
@@ -28,7 +28,9 @@ export function BehinderTabContent({
name="behinderPass"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.behinderPass")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.behinderPass")} {t("optional")}
</FormLabel>
<Input {...field} placeholder={t("placeholders.input")} className="h-8" />
</FormItem>
)}
@@ -49,7 +51,9 @@ export function BehinderTabContent({
name="headerValue"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.headerValue")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.headerValue")} {t("optional")}
</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.headerValue")} className="h-8" />
</FormItem>
)}
+3 -1
View File
@@ -28,7 +28,9 @@ export function CommandTabContent({
name="commandParamName"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.paramName")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.paramName")} {t("optional")}
</FormLabel>
<FormControl>
<Input {...field} placeholder={t("shellToolConfig.paramName")} className="h-8" />
</FormControl>
+9 -3
View File
@@ -29,7 +29,9 @@ export function GodzillaTabContent({
name="godzillaPass"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.pass")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.pass")} {t("optional")}
</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.pass")} className="h-8" />
</FormItem>
)}
@@ -39,7 +41,9 @@ export function GodzillaTabContent({
name="godzillaKey"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.key")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.key")} {t("optional")}
</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.key")} className="h-8" />
</FormItem>
)}
@@ -59,7 +63,9 @@ export function GodzillaTabContent({
name="headerValue"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.headerValue")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.headerValue")} {t("optional")}
</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.headerValue")} className="h-8" />
</FormItem>
)}
+3 -1
View File
@@ -39,7 +39,9 @@ export function NeoRegTabContent({
name="headerValue"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.headerValue")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.headerValue")} {t("optional")}
</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.headerValue")} className="h-8" />
</FormItem>
)}
+3 -1
View File
@@ -39,7 +39,9 @@ export function Suo5TabContent({
name="headerValue"
render={({ field }) => (
<FormItem className="gap-1">
<FormLabel className="h-6 flex items-center gap-1">{t("shellToolConfig.headerValue")}</FormLabel>
<FormLabel className="h-6 flex items-center gap-1">
{t("shellToolConfig.headerValue")} {t("optional")}
</FormLabel>
<Input {...field} placeholder={t("shellToolConfig.headerValue")} className="h-8" />
</FormItem>
)}
+3 -3
View File
@@ -3,10 +3,10 @@ import "./index.css";
import { TailwindIndicator } from "@/components/tailwind-indicator.tsx";
import { env } from "@/config.ts";
import { I18nextProvider } from "react-i18next";
import i18n from "./i18n/i18n";
import { RouterProvider } from "react-router-dom";
import { router } from "./router";
import i18n from "./i18n/i18n";
import { QueryProvider } from "./providers/query-client-provider";
import { router } from "./router";
const rootElement = document.getElementById("app") as HTMLElement;
@@ -18,6 +18,6 @@ if (!rootElement.innerHTML) {
<RouterProvider router={router} />
{env.MODE !== "production" && <TailwindIndicator />}
</I18nextProvider>
</QueryProvider>
</QueryProvider>,
);
}
+25 -20
View File
@@ -7,7 +7,6 @@ import { env } from "@/config.ts";
import { FormSchema, formSchema } from "@/types/schema.ts";
import {
APIErrorResponse,
ConfigResponseType,
GenerateResponse,
GenerateResult,
MainConfig,
@@ -17,14 +16,17 @@ import {
} from "@/types/shell.ts";
import { customValidation, transformToPostData } from "@/utils/transformer.ts";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { LoaderCircle, WandSparklesIcon } from "lucide-react";
import { useState, useTransition } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useLoaderData } from "react-router-dom";
import { toast } from "sonner";
import { useQuery } from "@tanstack/react-query";
export default function IndexPage() {
const urlParams = useLoaderData() as Partial<FormSchema>;
const { data: serverConfig } = useQuery<ServerConfig>({
queryKey: ["serverConfig"],
queryFn: async () => {
@@ -53,23 +55,25 @@ export default function IndexPage() {
const form = useForm<FormSchema>({
resolver: zodResolver(formSchema),
defaultValues: {
server: "Tomcat",
targetJdkVersion: "50",
debug: false,
bypassJavaModule: false,
shellClassName: "",
shellTool: "Godzilla",
shellType: "Listener",
urlPattern: "/*",
godzillaPass: "pass",
godzillaKey: "key",
commandParamName: "cmd",
behinderPass: "pass",
antSwordPass: "ant",
headerName: "User-Agent",
headerValue: "test",
injectorClassName: "",
packingMethod: "Base64",
server: urlParams.server ?? "Tomcat",
targetJdkVersion: urlParams.targetJdkVersion ?? "50",
debug: urlParams.debug ?? false,
bypassJavaModule: urlParams.bypassJavaModule ?? false,
shellClassName: urlParams.shellClassName ?? "",
shellTool: urlParams.shellTool ?? ShellToolType.Godzilla,
shellType: urlParams.shellType ?? "Listener",
urlPattern: urlParams.urlPattern ?? "/*",
godzillaPass: urlParams.godzillaPass ?? "",
godzillaKey: urlParams.godzillaKey ?? "",
commandParamName: urlParams.commandParamName ?? "",
behinderPass: urlParams.behinderPass ?? "",
antSwordPass: urlParams.antSwordPass ?? "",
headerName: urlParams.headerName ?? "User-Agent",
headerValue: urlParams.headerValue ?? "",
injectorClassName: urlParams.injectorClassName ?? "",
packingMethod: urlParams.packingMethod ?? "",
shrink: urlParams.shrink ?? false,
shellClassBase64: urlParams.shellClassBase64 ?? "",
},
});
@@ -95,6 +99,7 @@ export default function IndexPage() {
if (!response.ok) {
const json: APIErrorResponse = await response.json();
toast.error(t("errors.generationFailed", { error: json.error }));
return;
}
const result = (await response.json()) as GenerateResponse;
@@ -131,4 +136,4 @@ export default function IndexPage() {
</form>
</Form>
);
}
}
+2 -2
View File
@@ -10,6 +10,6 @@ const queryClient = new QueryClient({
},
});
export function QueryProvider({ children }: { children: ReactNode }) {
export function QueryProvider({ children }: Readonly<{ children: ReactNode }>) {
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
}
}
+45 -4
View File
@@ -1,7 +1,44 @@
import { createBrowserRouter } from "react-router-dom";
import { env } from "@/config";
import RootLayout from "@/components/layouts/root-layout";
import { env } from "@/config";
import IndexPage from "@/pages";
import { FormSchema } from "@/types/schema";
import { createBrowserRouter } from "react-router-dom";
// Function to parse URL parameters into form default values
const parseUrlParams = (searchParams: URLSearchParams): Partial<FormSchema> => {
const result: Partial<FormSchema> = {};
// Helper function to parse boolean values
const parseBoolean = (value: string | null): boolean | undefined => {
if (value === null) return undefined;
return value.toLowerCase() === "true";
};
// Map URL parameters to form fields
if (searchParams.has("server")) result.server = searchParams.get("server") ?? undefined;
if (searchParams.has("targetJdkVersion")) result.targetJdkVersion = searchParams.get("targetJdkVersion") ?? undefined;
if (searchParams.has("debug")) result.debug = parseBoolean(searchParams.get("debug"));
if (searchParams.has("bypassJavaModule"))
result.bypassJavaModule = parseBoolean(searchParams.get("bypassJavaModule"));
if (searchParams.has("shellClassName")) result.shellClassName = searchParams.get("shellClassName") ?? undefined;
if (searchParams.has("shellTool")) result.shellTool = searchParams.get("shellTool") ?? undefined;
if (searchParams.has("shellType")) result.shellType = searchParams.get("shellType") ?? undefined;
if (searchParams.has("urlPattern")) result.urlPattern = searchParams.get("urlPattern") ?? undefined;
if (searchParams.has("godzillaPass")) result.godzillaPass = searchParams.get("godzillaPass") ?? undefined;
if (searchParams.has("godzillaKey")) result.godzillaKey = searchParams.get("godzillaKey") ?? undefined;
if (searchParams.has("behinderPass")) result.behinderPass = searchParams.get("behinderPass") ?? undefined;
if (searchParams.has("antSwordPass")) result.antSwordPass = searchParams.get("antSwordPass") ?? undefined;
if (searchParams.has("commandParamName")) result.commandParamName = searchParams.get("commandParamName") ?? undefined;
if (searchParams.has("headerName")) result.headerName = searchParams.get("headerName") ?? undefined;
if (searchParams.has("headerValue")) result.headerValue = searchParams.get("headerValue") ?? undefined;
if (searchParams.has("injectorClassName"))
result.injectorClassName = searchParams.get("injectorClassName") ?? undefined;
if (searchParams.has("packingMethod")) result.packingMethod = searchParams.get("packingMethod") ?? undefined;
if (searchParams.has("shrink")) result.shrink = parseBoolean(searchParams.get("shrink"));
if (searchParams.has("shellClassBase64")) result.shellClassBase64 = searchParams.get("shellClassBase64") ?? undefined;
return result;
};
export const router = createBrowserRouter(
[
@@ -12,11 +49,15 @@ export const router = createBrowserRouter(
{
index: true,
element: <IndexPage />,
loader: ({ request }) => {
const url = new URL(request.url);
return parseUrlParams(url.searchParams);
},
},
],
},
],
{
basename: env.BASE_PATH,
}
);
},
);
+40
View File
@@ -56,3 +56,43 @@ export function transformToPostData(formValue: FormSchema) {
packer: formValue.packingMethod,
};
}
/**
* Generates a URL with the current form values as query parameters
* @param values The form values
* @returns A URL string with query parameters
*/
export function generateShareableUrl(values: FormSchema): string {
const params = new URLSearchParams();
// Helper function to add parameters only if they have non-default values
const addParam = (key: string, value: any, defaultValue: any) => {
if (value !== defaultValue) {
params.append(key, String(value));
}
};
// Add all form values to the URL parameters
addParam("server", values.server, "Tomcat");
addParam("targetJdkVersion", values.targetJdkVersion, "50");
addParam("debug", values.debug, false);
addParam("bypassJavaModule", values.bypassJavaModule, false);
addParam("shellClassName", values.shellClassName, "");
addParam("shellTool", values.shellTool, "Godzilla");
addParam("shellType", values.shellType, "Listener");
addParam("urlPattern", values.urlPattern, "/*");
addParam("godzillaPass", values.godzillaPass, "pass");
addParam("godzillaKey", values.godzillaKey, "key");
addParam("commandParamName", values.commandParamName, "cmd");
addParam("behinderPass", values.behinderPass, "pass");
addParam("antSwordPass", values.antSwordPass, "ant");
addParam("headerName", values.headerName, "User-Agent");
addParam("headerValue", values.headerValue, "test");
addParam("injectorClassName", values.injectorClassName, "");
addParam("packingMethod", values.packingMethod, "Base64");
addParam("shrink", values.shrink, false);
addParam("shellClassBase64", values.shellClassBase64, "");
// Return the current URL with the query parameters
return `${window.location.origin}${window.location.pathname}?${params.toString()}`;
}