tr]:last:border-b-0", className)}
+ {...props}
+ />
+ );
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+ return (
+
+ );
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+ return (
+ [role=checkbox]]:translate-y-[2px]",
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+ | [role=checkbox]]:translate-y-[2px]",
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
+ return (
+
+ );
+}
+
+export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
diff --git a/web/src/components/ui/tabs.tsx b/web/src/components/ui/tabs.tsx
index 6b106467..bf7388e5 100644
--- a/web/src/components/ui/tabs.tsx
+++ b/web/src/components/ui/tabs.tsx
@@ -1,5 +1,5 @@
+import { Tabs as TabsPrimitive } from "radix-ui";
import * as React from "react";
-import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "@/lib/utils";
diff --git a/web/src/components/ui/tooltip.tsx b/web/src/components/ui/tooltip.tsx
index f6f6940e..6ce648a7 100644
--- a/web/src/components/ui/tooltip.tsx
+++ b/web/src/components/ui/tooltip.tsx
@@ -1,5 +1,5 @@
+import { Tooltip as TooltipPrimitive } from "radix-ui";
import * as React from "react";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
diff --git a/web/src/components/version-badge.tsx b/web/src/components/version-badge.tsx
index 60f923c5..df6c120f 100644
--- a/web/src/components/version-badge.tsx
+++ b/web/src/components/version-badge.tsx
@@ -1,8 +1,8 @@
-import { env } from "@/config";
import { useQuery } from "@tanstack/react-query";
import { CircleX, LoaderCircle, RefreshCcw } from "lucide-react";
import type React from "react";
import { useTranslation } from "react-i18next";
+import { env } from "@/config";
import { Button } from "./ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
diff --git a/web/src/index.css b/web/src/index.css
index 38b8c751..3c9dbc24 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -45,6 +45,11 @@
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
+ --color-1: oklch(66.2% 0.225 25.9);
+ --color-2: oklch(60.4% 0.26 302);
+ --color-3: oklch(69.6% 0.165 251);
+ --color-4: oklch(80.2% 0.134 225);
+ --color-5: oklch(90.7% 0.231 133);
}
.dark {
@@ -79,6 +84,11 @@
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
+ --color-1: oklch(66.2% 0.225 25.9);
+ --color-2: oklch(60.4% 0.26 302);
+ --color-3: oklch(69.6% 0.165 251);
+ --color-4: oklch(80.2% 0.134 225);
+ --color-5: oklch(90.7% 0.231 133);
}
@theme inline {
@@ -118,6 +128,29 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
+ --animate-line-shadow: line-shadow 15s linear infinite;
+ @keyframes line-shadow {
+ 0% {
+ background-position: 0 0;
+ }
+ 100% {
+ background-position: 100% -100%;
+ }
+ }
+ --animate-rainbow: rainbow var(--speed, 2s) infinite linear;
+ --color-color-5: var(--color-5);
+ --color-color-4: var(--color-4);
+ --color-color-3: var(--color-3);
+ --color-color-2: var(--color-2);
+ --color-color-1: var(--color-1);
+ @keyframes rainbow {
+ 0% {
+ background-position: 0%;
+ }
+ 100% {
+ background-position: 200%;
+ }
+ }
}
@layer base {
diff --git a/web/src/lib/config.ts b/web/src/lib/config.ts
new file mode 100644
index 00000000..876445e1
--- /dev/null
+++ b/web/src/lib/config.ts
@@ -0,0 +1,15 @@
+export const siteConfig = {
+ name: "MemShellParty",
+ url: "https://party.memshell.news",
+ github: "https://github.com/ReaJason/MemShellParty",
+ navItems: [
+ {
+ href: "/memshell",
+ label: "MemShellGenerator",
+ },
+ {
+ href: "/detection",
+ label: "DetectionGenerator",
+ },
+ ],
+};
diff --git a/web/src/main.tsx b/web/src/main.tsx
index 6370b0af..15a63c8f 100644
--- a/web/src/main.tsx
+++ b/web/src/main.tsx
@@ -1,9 +1,9 @@
import ReactDOM from "react-dom/client";
import "./index.css";
-import { TailwindIndicator } from "@/components/tailwind-indicator.tsx";
-import { env } from "@/config.ts";
import { I18nextProvider } from "react-i18next";
import { RouterProvider } from "react-router-dom";
+import { TailwindIndicator } from "@/components/tailwind-indicator.tsx";
+import { env } from "@/config.ts";
import i18n from "./i18n/i18n";
import { QueryProvider } from "./providers/query-client-provider";
import { router } from "./router";
diff --git a/web/src/pages/detection.tsx b/web/src/pages/detection.tsx
new file mode 100644
index 00000000..0152f8ab
--- /dev/null
+++ b/web/src/pages/detection.tsx
@@ -0,0 +1,802 @@
+/** biome-ignore-all lint/suspicious/noThenProperty: no way */
+
+import { yupResolver } from "@hookform/resolvers/yup";
+import { AlertTriangle, Trash2 } from "lucide-react";
+import * as React from "react";
+import { useId, useState } from "react";
+import { Controller, useFieldArray, useForm } from "react-hook-form";
+import * as yup from "yup";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardFooter } from "@/components/ui/card";
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormFieldItem,
+ FormFieldLabel,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { Textarea } from "@/components/ui/textarea";
+
+// --- 类型定义 ---
+
+// 回显方式
+type EchoMethod = "header" | "body" | "dnslog" | "httplog" | "sleep";
+
+// 回显内容
+type EchoContent = "middleware" | "os" | "jdk" | "bytecode" | "command";
+
+// 表单值的完整类型
+interface PayloadFormValues {
+ echoMethod: EchoMethod;
+ echoContent?: EchoContent;
+ // Header
+ headerName?: string;
+ // DNSLog / HTTPLog
+ logAddress?: string;
+ middlewareType?: "tomcat" | "jetty" | "springboot" | "weblogic";
+ paramSource?: "builtin" | "requestParam" | "requestHeader";
+ requestParamName?: string;
+ requestHeaderName?: string;
+ bytecodeBase64?: string;
+ command?: string;
+ // Sleep 选项
+ sleepMiddleware?: "tomcat" | "jetty" | "springboot" | "weblogic";
+ sleepMiddlewareDelay?: number;
+ sleepOsDelays?: { os: "linux" | "windows" | "macos"; delay: number }[];
+ sleepJdkDetectionType?: "version" | "type";
+ sleepJdkVersion?: "8" | "11" | "17";
+ sleepJdkType?: "jdk" | "jre";
+ sleepJdkDelay?: number;
+ sleepBytecodeSuccessDelay?: number;
+}
+
+const formSchema = yup.object().shape({
+ echoMethod: yup.string().required("请选择一种回显方式"),
+
+ // 条件:当 echoMethod 是 'header'
+ headerName: yup.string().when("echoMethod", {
+ is: "header",
+ then: (schema) => schema.required("必须填写 Header Name"),
+ otherwise: (schema) => schema.optional(),
+ }),
+
+ // 条件:当 echoMethod 是 'dnslog' 或 'httplog'
+ logAddress: yup.string().when("echoMethod", {
+ is: (val: EchoMethod) => val === "dnslog" || val === "httplog",
+ then: (schema) => schema.required("必须填写 Log 地址"),
+ otherwise: (schema) => schema.optional(),
+ }),
+
+ // 只有在选择了具体内容时才需要
+ echoContent: yup.string().required("请选择回显内容"),
+
+ // --- 嵌套条件验证 ---
+
+ // 中间件类型在特定组合下是必须的
+ middlewareType: yup.string().when(["echoMethod", "echoContent"], {
+ is: (echoMethod: EchoMethod, echoContent: EchoContent) =>
+ (echoMethod === "header" || echoMethod === "body") &&
+ (echoContent === "os" || echoContent === "jdk" || echoContent === "bytecode"),
+ then: (schema) => schema.required("请选择中间件类型"),
+ otherwise: (schema) => schema.optional(),
+ }),
+
+ // 字节码相关
+ paramSource: yup.string().when(["echoMethod", "echoContent"], {
+ is: (echoMethod: EchoMethod, echoContent: EchoContent) =>
+ (echoMethod === "header" || echoMethod === "body") && echoContent === "bytecode",
+ then: (schema) => schema.required("请选择字节码来源"),
+ otherwise: (schema) => schema.optional(),
+ }),
+ bytecodeBase64: yup.string().when(["echoContent", "paramSource"], {
+ is: (echoContent: EchoContent, paramSource: string) => echoContent === "bytecode" && paramSource === "builtin",
+ then: (schema) => schema.required("必须填写字节码 Base64"),
+ otherwise: (schema) => schema.optional(),
+ }),
+ requestParamName: yup.string().when("paramSource", {
+ is: (paramSource: string) => paramSource === "requestParam",
+ then: (schema) => schema.required("必须填写 Request Param Name"),
+ otherwise: (schema) => schema.optional(),
+ }),
+ requestHeaderName: yup.string().when("paramSource", {
+ is: (paramSource: string) => paramSource === "requestHeader",
+ then: (schema) => schema.required("必须填写 Request Header Name"),
+ otherwise: (schema) => schema.optional(),
+ }),
+ command: yup.string().when("echoContent", {
+ is: (echoContent: EchoContent) => echoContent === "command",
+ then: (schema) => schema.required("必须填写执行的命令"),
+ otherwise: (schema) => schema.optional(),
+ }),
+
+ // --- Sleep 相关验证 ---
+ sleepMiddleware: yup.string().when(["echoMethod", "echoContent"], {
+ is: (method: string, content: string) => method === "sleep" && content === "middleware",
+ then: (s) => s.required("请选择目标中间件"),
+ otherwise: (s) => s.optional(),
+ }),
+ sleepMiddlewareDelay: yup.number().when(["echoMethod", "echoContent"], {
+ is: (method: string, content: string) => method === "sleep" && content === "middleware",
+ then: (s) => s.required("请填写延迟时间").min(1, "延迟至少为 1 秒").typeError("请输入有效的秒数"),
+ otherwise: (s) => s.optional(),
+ }),
+ sleepOsDelays: yup.array().when(["echoMethod", "echoContent"], {
+ is: (method: string, content: string) => method === "sleep" && content === "os",
+ then: (s) =>
+ s
+ .of(
+ yup.object().shape({
+ os: yup.string().required(),
+ delay: yup.number().required("请填写延迟时间").min(1, "延迟至少为 1 秒").typeError("请输入有效的秒数"),
+ }),
+ )
+ .min(1, "至少需要一个操作系统延迟配置"),
+ otherwise: (s) => s.optional(),
+ }),
+ sleepJdkDetectionType: yup.string().when(["echoMethod", "echoContent"], {
+ is: (method: string, content: string) => method === "sleep" && content === "jdk",
+ then: (s) => s.required("请选择 JDK 探测类型"),
+ otherwise: (s) => s.optional(),
+ }),
+ sleepJdkVersion: yup.string().when("sleepJdkDetectionType", {
+ is: "version",
+ then: (s) => s.required("请选择 JDK 版本"),
+ otherwise: (s) => s.optional(),
+ }),
+ sleepJdkType: yup.string().when("sleepJdkDetectionType", {
+ is: "type",
+ then: (s) => s.required("请选择 JDK/JRE 类型"),
+ otherwise: (s) => s.optional(),
+ }),
+ sleepJdkDelay: yup.number().when(["echoMethod", "echoContent"], {
+ is: (method: string, content: string) => method === "sleep" && content === "jdk",
+ then: (s) => s.required("请填写延迟时间").min(1, "延迟至少为 1 秒").typeError("请输入有效的秒数"),
+ otherwise: (s) => s.optional(),
+ }),
+ sleepBytecodeSuccessDelay: yup.number().when(["echoMethod", "echoContent"], {
+ is: (method: string, content: string) => method === "sleep" && content === "bytecode",
+ then: (s) => s.required("请填写延迟时间").min(1, "延迟至少为 1 秒").typeError("请输入有效的秒数"),
+ otherwise: (s) => s.optional(),
+ }),
+});
+
+export default function EchoPayloadGenerator() {
+ const form = useForm({
+ resolver: yupResolver(formSchema) as any,
+ defaultValues: {
+ echoMethod: "header",
+ echoContent: undefined,
+ headerName: "X-Echo",
+ logAddress: "",
+ middlewareType: undefined,
+ paramSource: "builtin",
+ requestHeaderName: "X-Data",
+ requestParamName: "payload",
+ sleepOsDelays: [{ os: "linux", delay: 5 }],
+ sleepJdkDetectionType: "version",
+ sleepJdkType: "jdk",
+ sleepJdkVersion: "8",
+ sleepJdkDelay: 5,
+ sleepBytecodeSuccessDelay: 5,
+ sleepMiddlewareDelay: 5,
+ },
+ });
+
+ const { fields, append, remove } = useFieldArray({
+ control: form.control,
+ name: "sleepOsDelays",
+ });
+
+ // 监视表单值的变化以动态渲染 UI
+ const watchedEchoMethod = form.watch("echoMethod");
+ const watchedEchoContent = form.watch("echoContent");
+ const watchedParamSource = form.watch("paramSource");
+ const watchedSleepJdkDetectionType = form.watch("sleepJdkDetectionType");
+
+ // 重置子选项
+ React.useEffect(() => {
+ form.setValue("echoContent", undefined);
+ }, [form.setValue]);
+
+ function onSubmit(values: PayloadFormValues) {
+ // 在这里可以构建最终的 Payload
+ console.log("Form Submitted:", values);
+ }
+
+ // --- 动态内容渲染 ---
+ const [isFile, setIsFile] = useState(false);
+ const optionOneId = useId();
+ const optionTwoId = useId();
+
+ const renderContentOptions = () => {
+ const baseOptions: { value: EchoContent; label: string }[] = [
+ { value: "middleware", label: "中间件类型" },
+ { value: "os", label: "操作系统类型" },
+ { value: "jdk", label: "JDK 信息" },
+ { value: "command", label: "命令执行" },
+ { value: "bytecode", label: "自定义字节码执行" },
+ ];
+
+ let filteredOptions = baseOptions;
+
+ // 根据规则过滤选项
+ if (watchedEchoMethod === "header" || watchedEchoMethod === "body") {
+ filteredOptions = baseOptions.filter((opt) => opt.value !== "middleware");
+ }
+
+ if (watchedEchoMethod === "dnslog" || watchedEchoMethod === "httplog" || watchedEchoMethod === "sleep") {
+ filteredOptions = baseOptions.filter((opt) => opt.value !== "command" && opt.value !== "bytecode");
+ }
+
+ return (
+ (
+
+ 回显内容
+
+
+
+ )}
+ />
+ );
+ };
+
+ const renderDynamicFields = () => {
+ // --- Response Header / Body ---
+ if ((watchedEchoMethod === "header" || watchedEchoMethod === "body") && watchedEchoContent) {
+ return (
+ <>
+ {watchedEchoContent === "bytecode" && (
+
+ )}
+ {watchedEchoContent === "command" && (
+
+ (
+
+ 命令来源
+
+
+
+
+
+
+ 内嵌命令
+
+
+
+
+
+ 通过 Request Header 传递
+
+
+
+
+
+ 通过 Request Param 传递
+
+
+
+
+
+ )}
+ />
+ {watchedParamSource === "builtin" && (
+ (
+
+ 执行命令
+
+
+
+
+
+ )}
+ />
+ )}
+ {watchedParamSource === "requestParam" && (
+ (
+
+ Request Param Name
+
+
+
+
+
+ )}
+ />
+ )}
+ {watchedParamSource === "requestHeader" && (
+ (
+
+ Request Header Name
+
+
+
+
+
+ )}
+ />
+ )}
+
+ )}
+ >
+ );
+ }
+
+ // --- Sleep Delay ---
+ if (watchedEchoMethod === "sleep" && watchedEchoContent) {
+ return (
+
+ {watchedEchoContent === "middleware" && (
+
+ (
+
+ 目标中间件
+
+
+
+ )}
+ />
+ (
+
+ 命中延迟时间 (秒)
+
+ field.onChange(+event.target.value)}
+ />
+
+
+
+ )}
+ />
+
+ )}
+
+ {watchedEchoContent === "os" && (
+
+ 操作系统与延迟时间
+ {fields.map((item, index) => (
+
+ (
+
+ )}
+ />
+ (
+ field.onChange(+event.target.value)}
+ />
+ )}
+ />
+
+
+ ))}
+
+ {form.formState.errors.sleepOsDelays?.message}
+
+ )}
+
+ {watchedEchoContent === "jdk" && (
+
+ (
+
+ JDK 探测类型
+
+
+
+
+
+
+ 按版本 (大于等于)
+
+
+
+
+
+ 按类型 (JDK/JRE)
+
+
+
+
+ )}
+ />
+ {watchedSleepJdkDetectionType === "version" && (
+ (
+
+ JDK 版本
+
+
+
+ )}
+ />
+ )}
+ {watchedSleepJdkDetectionType === "type" && (
+ (
+
+ 目标类型
+
+
+
+ )}
+ />
+ )}
+ (
+
+ 命中延迟时间 (秒)
+
+ field.onChange(+event.target.value)}
+ />
+
+
+
+ )}
+ />
+
+ )}
+
+ {watchedEchoContent === "bytecode" && (
+ (
+
+ 执行成功延迟时间 (秒)
+
+ field.onChange(+event.target.value)}
+ />
+
+
+
+ )}
+ />
+ )}
+
+ );
+ }
+
+ return null;
+ };
+
+ return (
+
+ );
+}
diff --git a/web/src/pages/landing.tsx b/web/src/pages/landing.tsx
new file mode 100644
index 00000000..dbd34992
--- /dev/null
+++ b/web/src/pages/landing.tsx
@@ -0,0 +1,296 @@
+import { motion } from "framer-motion";
+import {
+ CombineIcon,
+ GithubIcon,
+ MinimizeIcon,
+ ServerIcon,
+ ShellIcon,
+ Shield,
+ ShieldCheckIcon,
+ TerminalIcon,
+ ZapIcon,
+} from "lucide-react";
+import { NavLink } from "react-router-dom";
+import { LineShadowText } from "@/components/magicui/line-shadow-text";
+import { RainbowButton } from "@/components/magicui/rainbow-button";
+import { useTheme } from "@/components/theme-provider";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+
+export default function MemShellPartyLandingPage() {
+ const features = [
+ {
+ icon: ,
+ title: "Non-Intrusive",
+ description:
+ "Generated memshells don't interfere with normal middleware traffic, even with multiple shells injected.",
+ },
+ {
+ icon: ,
+ title: "High Availability",
+ description: "Comes with comprehensive CI integration tests, covering mainstream middleware for reliability.",
+ },
+ {
+ icon: ,
+ title: "Minimal Size",
+ description: "The core code of the memshell is kept as concise as possible for efficient transfer and injection.",
+ },
+ {
+ icon: ,
+ title: "Strong Compatibility",
+ description: "Covers common middleware and frameworks like Tomcat, WebLogic, and Spring.",
+ },
+ ];
+
+ const supportedMiddleware = [
+ "Tomcat",
+ "Jetty",
+ "GlassFish",
+ "Payara",
+ "Resin",
+ "SpringWebMVC",
+ "SpringWebFlux",
+ "XXL-JOB",
+ "JBossAS",
+ "JBossEAP",
+ "WildFly",
+ "Undertow",
+ "WebLogic",
+ "WebSphere",
+ "BES",
+ "TongWeb",
+ "InforSuite AS",
+ "Apusic AS",
+ "Primeton",
+ ];
+ const supportedShells = [
+ "Godzilla",
+ "Behinder",
+ "AntSword",
+ "Suo5",
+ "Neo-reGeorg",
+ "Command Execution",
+ "Custom Shell",
+ ];
+
+ const theme = useTheme();
+ const shadowColor = theme.theme === "dark" ? "#ffffff" : "#000000";
+
+ return (
+
+
+
+
+
+
+
+ For Security Research & Authorized Testing Only
+
+
+
+ MemShell
+
+ Party
+
+
+
+
+ A self-hosted, visual platform for one-click generation of Java memory shells for common middleware and
+ frameworks. The ultimate learning platform for security researchers.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Why Choose MemShellParty?
+ Designed for modern offensive and defensive scenarios.
+
+
+ {features.map((feature, index) => (
+
+
+
+ {feature.icon}
+ {feature.title}
+
+
+ {feature.description}
+
+
+
+ ))}
+
+
+
+
+
+
+
+ Extensive Support Matrix
+
+ Compatible with a wide range of industry-standard technologies.
+
+
+
+
+
+
+
+
+ Supported Middleware
+
+
+
+ {supportedMiddleware.map((tech) => (
+
+ {tech}
+
+ ))}
+
+
+
+
+
+
+
+
+ Supported Shell Tools
+
+
+
+ {supportedShells.map((shell) => (
+
+ {shell}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ Get Your Shell in 3 Simple Steps
+ A streamlined process from selection to generation.
+
+
+
+
+ 1
+
+ Select Middleware
+ Choose your target environment like Tomcat or Spring.
+
+
+
+ 2
+
+ Configure Shell
+ Pick a shell type and set parameters like passwords or keys.
+
+
+
+ 3
+
+ Generate Payload
+ Click generate to get the final payload for your operation.
+
+
+
+
+
+ {/* --- Final CTA Section --- */}
+
+
+ Ready to Join the Party?
+
+ Visit the GitHub repository to start your memshell exploration, or contribute to the project!
+
+
+
+
+
+ {/* --- Footer --- */}
+
+
+ );
+}
diff --git a/web/src/pages/index.tsx b/web/src/pages/memshell.tsx
similarity index 99%
rename from web/src/pages/index.tsx
rename to web/src/pages/memshell.tsx
index dc66e56f..b1f1dfea 100644
--- a/web/src/pages/index.tsx
+++ b/web/src/pages/memshell.tsx
@@ -1,3 +1,10 @@
+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 { MainConfigCard } from "@/components/main-config-card.tsx";
import { PackageConfigCard } from "@/components/package-config-card.tsx";
import { ShellResult } from "@/components/shell-result.tsx";
@@ -15,15 +22,8 @@ import {
ShellToolType,
} from "@/types/shell.ts";
import { transformToPostData } from "@/utils/transformer.ts";
-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";
-export default function IndexPage() {
+export default function MemShellPage() {
const urlParams = useLoaderData();
const { data: serverConfig } = useQuery({
diff --git a/web/src/router.tsx b/web/src/router.tsx
index 8e7b9f1e..7fbd4dcf 100644
--- a/web/src/router.tsx
+++ b/web/src/router.tsx
@@ -1,8 +1,10 @@
+import { createBrowserRouter } from "react-router-dom";
import RootLayout from "@/components/layouts/root-layout";
import { env } from "@/config";
-import IndexPage from "@/pages";
+import MemShellPage from "@/pages/memshell";
import { FormSchema } from "@/types/schema";
-import { createBrowserRouter } from "react-router-dom";
+import DetectionPage from "./pages/detection";
+import MemShellPartyLandingPage from "./pages/landing";
// Function to parse URL parameters into form default values
const parseUrlParams = (searchParams: URLSearchParams): Partial => {
@@ -48,12 +50,20 @@ export const router = createBrowserRouter(
children: [
{
index: true,
- element: ,
+ element: ,
+ },
+ {
+ path: "memshell",
+ element: ,
loader: ({ request }) => {
const url = new URL(request.url);
return parseUrlParams(url.searchParams);
},
},
+ {
+ path: "detection",
+ element: ,
+ },
],
},
],
diff --git a/web/src/types/schema.ts b/web/src/types/schema.ts
index cc80030e..63e095f7 100644
--- a/web/src/types/schema.ts
+++ b/web/src/types/schema.ts
@@ -80,15 +80,14 @@ export const useYupValidationResolver = (validationSchema: yup.ObjectSchema
return {
values: {} as FormSchema,
errors: errors.inner.reduce(
- (allErrors, currentError) => ({
- // biome-ignore lint/performance/noAccumulatingSpread:
- ...allErrors,
- [currentError.path as keyof FormSchema]: {
+ (allErrors, currentError) => {
+ allErrors[currentError.path as keyof FormSchema] = {
type: currentError.type ?? "validation",
message: currentError.message,
- },
- }),
- {},
+ };
+ return allErrors;
+ },
+ {} as FieldErrors,
),
};
}
|