feat: add more page

This commit is contained in:
ReaJason
2025-08-13 23:53:40 +08:00
parent 8c06216e96
commit c925954035
60 changed files with 2403 additions and 284 deletions
+15 -8
View File
@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "$schema": "https://biomejs.dev/schemas/2.0.5/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",
@@ -13,7 +13,17 @@
"noExplicitAny": "off" "noExplicitAny": "off"
}, },
"style": { "style": {
"useImportType": "off" "useImportType": "off",
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
}, },
"recommended": true, "recommended": true,
"a11y": { "a11y": {
@@ -24,19 +34,16 @@
"noUnusedImports": "warn" "noUnusedImports": "warn"
} }
}, },
"ignore": ["node_modules", "tsconfig*", "dist", "components/ui/**"] "includes": ["**", "!**/node_modules", "!**/tsconfig*", "!**/dist", "!**/components/ui/**"]
}, },
"formatter": { "formatter": {
"indentStyle": "space", "indentStyle": "space",
"indentWidth": 2, "indentWidth": 2,
"enabled": true, "enabled": true,
"lineWidth": 120, "lineWidth": 120,
"ignore": ["node_modules", "tsconfig*", "dist"] "includes": ["**", "!**/node_modules", "!**/tsconfig*", "!**/dist"]
},
"organizeImports": {
"enabled": true,
"ignore": ["node_modules", "dist", "components/ui/**"]
}, },
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"javascript": { "javascript": {
"formatter": { "formatter": {
"quoteStyle": "double" "quoteStyle": "double"
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { cp } from "node:fs/promises"; import { cp } from "node:fs/promises";
import { join, resolve } from "node:path"; import { join, resolve } from "node:path";
+20 -41
View File
@@ -14,67 +14,46 @@
"lint:fix": "bunx @biomejs/biome check ./ --write" "lint:fix": "bunx @biomejs/biome check ./ --write"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "2.0.5",
"@types/node": "^22.15.29", "@types/node": "^24.0.4",
"@types/react": "^19.1.6", "@types/react": "^19.1.8",
"@types/react-copy-to-clipboard": "^5.0.7", "@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.1.6",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
"@vitejs/plugin-react": "^4.5.1", "@vitejs/plugin-react": "^4.6.0",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"tailwindcss": "^4.1.8", "tailwindcss": "^4.1.10",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^6.3.5", "vite": "^7.0.0",
"vite-bundle-visualizer": "^1.2.1" "vite-bundle-visualizer": "^1.2.1"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.0.1", "@hookform/resolvers": "^5.1.1",
"@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-aspect-ratio": "^1.1.7",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-context-menu": "^2.2.15",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.14",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-menubar": "^1.1.15",
"@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-navigation-menu": "^1.2.13",
"@radix-ui/react-popover": "^1.1.14", "@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-progress": "^1.1.7", "@tailwindcss/vite": "^4.1.10",
"@radix-ui/react-radio-group": "^1.3.7", "@tanstack/react-query": "^5.81.2",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/vite": "^4.1.8",
"@tanstack/react-query": "^5.80.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "framer-motion": "^12.19.1",
"i18next": "^25.2.1", "i18next": "^25.2.1",
"lucide-react": "^0.513.0", "lucide-react": "^0.523.0",
"motion": "^12.19.1",
"radix-ui": "^1.4.2",
"react": "^19.1.0", "react": "^19.1.0",
"react-copy-to-clipboard": "^5.1.0", "react-copy-to-clipboard": "^5.1.0",
"react-day-picker": "9.7.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-hook-form": "^7.57.0", "react-hook-form": "^7.58.1",
"react-i18next": "^15.5.2", "react-i18next": "^15.5.3",
"react-router": "^7.6.2", "react-router": "^7.6.2",
"react-router-dom": "^7.6.2", "react-router-dom": "^7.6.2",
"react-syntax-highlighter": "^15.6.1", "react-syntax-highlighter": "^15.6.1",
"sonner": "^2.0.5", "sonner": "^2.0.5",
"tailwind-merge": "^3.3.0", "tailwind-merge": "^3.3.1",
"tailwind-scrollbar": "^4.0.2",
"tw-animate-css": "^1.3.4", "tw-animate-css": "^1.3.4",
"yup": "^1.6.1", "yup": "^1.6.1"
"zod": "^3.25.51"
}, },
"trustedDependencies": ["@biomejs/biome"] "trustedDependencies": [
"@biomejs/biome"
]
} }
+2 -2
View File
@@ -1,5 +1,3 @@
import { Button, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { VariantProps } from "class-variance-authority"; import { VariantProps } from "class-variance-authority";
import { Check, Copy } from "lucide-react"; import { Check, Copy } from "lucide-react";
import { type HTMLProps, type ReactNode, useCallback, useEffect, useState } from "react"; import { type HTMLProps, type ReactNode, useCallback, useEffect, useState } from "react";
@@ -8,6 +6,8 @@ import { useTranslation } from "react-i18next";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button, buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
interface CopyButtonProps extends React.ComponentProps<"button"> { interface CopyButtonProps extends React.ComponentProps<"button"> {
value: string; value: string;
+2 -2
View File
@@ -1,10 +1,10 @@
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Check, Copy } from "lucide-react"; import { Check, Copy } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard"; import { CopyToClipboard } from "react-copy-to-clipboard";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
interface CopyableFieldProps { interface CopyableFieldProps {
label: string; label: string;
+18 -5
View File
@@ -1,3 +1,5 @@
import { NavLink } from "react-router";
import { Outlet } from "react-router-dom";
import { LanguageSwitcher } from "@/components/language-switcher"; import { LanguageSwitcher } from "@/components/language-switcher";
import { ModeToggle } from "@/components/mode-toggle.tsx"; import { ModeToggle } from "@/components/mode-toggle.tsx";
import { ThemeProvider } from "@/components/theme-provider.tsx"; import { ThemeProvider } from "@/components/theme-provider.tsx";
@@ -5,7 +7,8 @@ import { Button } from "@/components/ui/button";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import VersionBadge from "@/components/version-badge"; import VersionBadge from "@/components/version-badge";
import { GitHubIcon } from "@/icon"; import { GitHubIcon } from "@/icon";
import { Outlet } from "react-router-dom"; import { siteConfig } from "@/lib/config";
import { MobileNav } from "../modile-nav";
export default function RootLayout() { export default function RootLayout() {
return ( return (
@@ -13,11 +16,21 @@ export default function RootLayout() {
<Toaster /> <Toaster />
<div className="flex flex-col h-screen"> <div className="flex flex-col h-screen">
<header className="sticky top-0 z-50 border-b bg-background px-2 sm:px-4 py-2 sm:py-3"> <header className="sticky top-0 z-50 border-b bg-background px-2 sm:px-4 py-2 sm:py-3">
<div className="flex flex-col sm:flex-row items-center justify-between gap-2 sm:gap-0"> <div className="flex items-center gap-2">
<div className="flex items-center"> <MobileNav className="flex lg:hidden" items={siteConfig.navItems} />
<h2 className="text-base sm:text-lg font-semibold text-center">MemShellParty - JavaWeb</h2> <div className="hidden lg:flex">
<NavLink to="/" className="text-base sm:text-lg font-semibold text-center">
MemShellParty
</NavLink>
</div> </div>
<div className="flex items-center gap-0.5 sm:gap-1"> <nav className="items-center gap-0.5 hidden lg:flex">
{siteConfig.navItems.map((item) => (
<Button key={item.href} variant="ghost" size="sm" asChild>
<NavLink to={item.href}>{item.label}</NavLink>
</Button>
))}
</nav>
<div className="ml-auto flex items-center gap-2 md:flex-1 md:justify-end">
<VersionBadge /> <VersionBadge />
<LanguageSwitcher /> <LanguageSwitcher />
<Button <Button
@@ -0,0 +1,40 @@
import { MotionProps, motion } from "motion/react";
import { cn } from "@/lib/utils";
interface LineShadowTextProps extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps>, MotionProps {
shadowColor?: string;
as?: React.ElementType;
}
export function LineShadowText({
children,
shadowColor = "black",
className,
as: Component = "span",
...props
}: LineShadowTextProps) {
const MotionComponent = motion.create(Component);
const content = typeof children === "string" ? children : null;
if (!content) {
throw new Error("LineShadowText only accepts string content");
}
return (
<MotionComponent
style={{ "--shadow-color": shadowColor } as React.CSSProperties}
className={cn(
"relative z-0 inline-flex",
"after:absolute after:left-[0.04em] after:top-[0.04em] after:content-[attr(data-text)]",
"after:bg-[linear-gradient(45deg,transparent_45%,var(--shadow-color)_45%,var(--shadow-color)_55%,transparent_0)]",
"after:-z-10 after:bg-[length:0.06em_0.06em] after:bg-clip-text after:text-transparent",
"after:animate-line-shadow",
className,
)}
data-text={content}
{...props}
>
{content}
</MotionComponent>
);
}
@@ -0,0 +1,59 @@
import { Slot } from "@radix-ui/react-slot";
import { cva, VariantProps } from "class-variance-authority";
import React from "react";
import { cn } from "@/lib/utils";
const rainbowButtonVariants = cva(
cn(
"relative cursor-pointer group transition-all animate-rainbow",
"inline-flex items-center justify-center gap-2 shrink-0",
"rounded-sm outline-none focus-visible:ring-[3px] aria-invalid:border-destructive",
"text-sm font-medium whitespace-nowrap",
"disabled:pointer-events-none disabled:opacity-50",
"[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
),
{
variants: {
variant: {
default:
"border-0 bg-[linear-gradient(#121213,#121213),linear-gradient(#121213_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] bg-[length:200%] text-primary-foreground [background-clip:padding-box,border-box,border-box] [background-origin:border-box] [border:calc(0.125rem)_solid_transparent] before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:animate-rainbow before:bg-[linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] before:[filter:blur(0.75rem)] dark:bg-[linear-gradient(#fff,#fff),linear-gradient(#fff_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))]",
outline:
"border border-input border-b-transparent bg-[linear-gradient(#ffffff,#ffffff),linear-gradient(#ffffff_50%,rgba(18,18,19,0.6)_80%,rgba(18,18,19,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] bg-[length:200%] text-accent-foreground [background-clip:padding-box,border-box,border-box] [background-origin:border-box] before:absolute before:bottom-[-20%] before:left-1/2 before:z-0 before:h-1/5 before:w-3/5 before:-translate-x-1/2 before:animate-rainbow before:bg-[linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))] before:[filter:blur(0.75rem)] dark:bg-[linear-gradient(#0a0a0a,#0a0a0a),linear-gradient(#0a0a0a_50%,rgba(255,255,255,0.6)_80%,rgba(0,0,0,0)),linear-gradient(90deg,var(--color-1),var(--color-5),var(--color-3),var(--color-4),var(--color-2))]",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-xl px-3 text-xs",
lg: "h-11 rounded-xl px-8",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
interface RainbowButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof rainbowButtonVariants> {
asChild?: boolean;
}
const RainbowButton = React.forwardRef<HTMLButtonElement, RainbowButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(rainbowButtonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
RainbowButton.displayName = "RainbowButton";
export { RainbowButton, rainbowButtonVariants, type RainbowButtonProps };
+138 -129
View File
@@ -1,3 +1,19 @@
import {
ArrowUpRightIcon,
AxeIcon,
CommandIcon,
NetworkIcon,
ServerIcon,
ShieldOffIcon,
SwordIcon,
WaypointsIcon,
ZapIcon,
} from "lucide-react";
import { JSX, useCallback, useEffect, useId, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { JreTip } from "@/components/tips/jre-tip.tsx";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { import {
FormControl, FormControl,
FormDescription, FormDescription,
@@ -13,23 +29,6 @@ import { Switch } from "@/components/ui/switch.tsx";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { FormSchema } from "@/types/schema.ts"; import { FormSchema } from "@/types/schema.ts";
import { JDKVersion, MainConfig, ServerConfig, ShellToolType } from "@/types/shell.ts"; import { JDKVersion, MainConfig, ServerConfig, ShellToolType } from "@/types/shell.ts";
import { JreTip } from "@/components/tips/jre-tip.tsx";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import {
ArrowUpRightIcon,
AxeIcon,
CommandIcon,
NetworkIcon,
ServerIcon,
ShieldOffIcon,
SwordIcon,
WaypointsIcon,
ZapIcon,
} from "lucide-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"; import { AntSwordTabContent } from "./tools/antsword-tab";
import { BehinderTabContent } from "./tools/behinder-tab"; import { BehinderTabContent } from "./tools/behinder-tab";
import { CommandTabContent } from "./tools/command-tab"; import { CommandTabContent } from "./tools/command-tab";
@@ -73,6 +72,51 @@ export function MainConfigCard({
const shellTool = form.watch("shellTool"); const shellTool = form.watch("shellTool");
const { t } = useTranslation(); const { t } = useTranslation();
// 处理一下 shellTypes 由于 server 或 shellTool 变更时无法正常为 form.shellType 赋值的问题
useEffect(() => {
if (shellTypes.length > 0) {
form.setValue("shellType", shellTypes[0]);
}
}, [shellTypes, form]);
const handleServerChange = useCallback(
(value: string) => {
if (mainConfig) {
const newShellToolMap = mainConfig[value];
setShellToolMap(newShellToolMap);
const newShellTools = Object.keys(newShellToolMap);
setShellTools([...newShellTools.map((tool) => tool as ShellToolType), ShellToolType.Custom]);
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
) {
form.setValue("targetJdkVersion", "52");
} else {
form.resetField("targetJdkVersion");
}
form.resetField("bypassJavaModule");
form.resetField("urlPattern");
}
},
[form, mainConfig],
);
// 处理一下默认值 server 不刷新 shellType 的问题 // 处理一下默认值 server 不刷新 shellType 的问题
useEffect(() => { useEffect(() => {
if (mainConfig) { if (mainConfig) {
@@ -81,120 +125,85 @@ export function MainConfigCard({
handleServerChange(initialServer); handleServerChange(initialServer);
} }
} }
}, [mainConfig, form]); }, [mainConfig, form, handleServerChange]);
// 处理一下 shellTypes 由于 server 或 shellTool 变更时无法正常为 form.shellType 赋值的问题 const handleShellToolChange = useCallback(
useEffect(() => { (value: string) => {
if (shellTypes.length > 0) { const resetCommand = () => {
form.setValue("shellType", shellTypes[0]); form.resetField("commandParamName");
} form.resetField("implementationClass");
}, [shellTypes, form]); form.resetField("encryptor");
};
const handleServerChange = (value: string) => { const resetGodzilla = () => {
if (mainConfig) { form.resetField("godzillaKey");
const newShellToolMap = mainConfig[value]; form.resetField("godzillaPass");
setShellToolMap(newShellToolMap); form.resetField("headerName");
form.resetField("headerValue");
};
const newShellTools = Object.keys(newShellToolMap); const resetBehinder = () => {
setShellTools([...newShellTools.map((tool) => tool as ShellToolType), ShellToolType.Custom]); form.resetField("behinderPass");
form.resetField("headerName");
form.resetField("headerValue");
};
const currentShellTool = form.getValues("shellTool"); const resetSuo5 = () => {
form.resetField("headerName");
form.resetField("headerValue");
};
const firstTool = newShellTools[0]; const resetAntSword = () => {
let currentShellTypes = null; form.resetField("antSwordPass");
form.resetField("headerName");
form.resetField("headerValue");
};
if (!newShellToolMap[currentShellTool]) { const resetNeoreGeorg = () => {
form.setValue("shellTool", firstTool); form.setValue("headerName", "Referer");
currentShellTypes = newShellToolMap[firstTool]; form.resetField("headerValue");
} else { };
currentShellTypes = newShellToolMap[currentShellTool];
const resetCustom = () => {
form.resetField("shellClassBase64");
};
if (shellToolMap) {
let currentShellTypes = null;
if (value === ShellToolType.Custom) {
currentShellTypes = servers?.[form.getValues("server")] as string[];
} else {
currentShellTypes = shellToolMap[value];
}
setShellTypes(currentShellTypes);
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();
}
} }
setShellTypes(currentShellTypes); form.setValue("shellTool", value);
},
[form, servers, shellToolMap],
);
// 特殊环境的 JDK 版本 const debugId = useId();
if ( const bypassId = useId();
(value === "SpringWebFlux" || value === "XXLJOB") && const shrinkId = useId();
Number.parseInt(form.getValues("targetJdkVersion") as string) < 52
) {
form.setValue("targetJdkVersion", "52");
} else {
form.resetField("targetJdkVersion");
}
form.resetField("bypassJavaModule");
form.resetField("urlPattern");
}
};
const handleShellToolChange = (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 (shellToolMap) {
let currentShellTypes = null;
if (value === ShellToolType.Custom) {
currentShellTypes = servers?.[form.getValues("server")] as string[];
} else {
currentShellTypes = shellToolMap[value];
}
setShellTypes(currentShellTypes);
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);
};
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
@@ -291,9 +300,9 @@ export function MainConfigCard({
render={({ field }) => ( render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0"> <FormItem className="flex items-center space-x-2 space-y-0">
<FormControl> <FormControl>
<Switch id="debug" checked={field.value} onCheckedChange={field.onChange} /> <Switch id={debugId} checked={field.value} onCheckedChange={field.onChange} />
</FormControl> </FormControl>
<FormLabel htmlFor="debug">{t("mainConfig.debug")}</FormLabel> <FormLabel htmlFor={debugId}>{t("mainConfig.debug")}</FormLabel>
</FormItem> </FormItem>
)} )}
/> />
@@ -303,9 +312,9 @@ export function MainConfigCard({
render={({ field }) => ( render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0"> <FormItem className="flex items-center space-x-2 space-y-0">
<FormControl> <FormControl>
<Switch id="bypassJavaModule" checked={field.value} onCheckedChange={field.onChange} /> <Switch id={bypassId} checked={field.value} onCheckedChange={field.onChange} />
</FormControl> </FormControl>
<Label htmlFor="bypassJavaModule">{t("mainConfig.bypassJavaModule")}</Label> <Label htmlFor={bypassId}>{t("mainConfig.bypassJavaModule")}</Label>
</FormItem> </FormItem>
)} )}
/> />
@@ -315,9 +324,9 @@ export function MainConfigCard({
render={({ field }) => ( render={({ field }) => (
<FormItem className="flex items-center space-x-2 space-y-0"> <FormItem className="flex items-center space-x-2 space-y-0">
<FormControl> <FormControl>
<Switch id="shrink" checked={field.value} onCheckedChange={field.onChange} /> <Switch id={shrinkId} checked={field.value} onCheckedChange={field.onChange} />
</FormControl> </FormControl>
<Label htmlFor="shrink">{t("mainConfig.shrink")}</Label> <Label htmlFor={shrinkId}>{t("mainConfig.shrink")}</Label>
</FormItem> </FormItem>
)} )}
/> />
+97
View File
@@ -0,0 +1,97 @@
import React from "react";
import { NavLink } from "react-router";
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
export function MobileNav({
items,
className,
}: Readonly<{
items: { href: string; label: string }[];
className?: string;
}>) {
const [open, setOpen] = React.useState(false);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="ghost"
className={cn(
"extend-touch-target h-8 touch-manipulation items-center justify-start gap-2.5 !p-0 hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 active:bg-transparent dark:hover:bg-transparent",
className,
)}
>
<div className="relative flex h-8 w-4 items-center justify-center">
<div className="relative size-4">
<span
className={cn(
"bg-foreground absolute left-0 block h-0.5 w-4 transition-all duration-100",
open ? "top-[0.4rem] -rotate-45" : "top-1",
)}
/>
<span
className={cn(
"bg-foreground absolute left-0 block h-0.5 w-4 transition-all duration-100",
open ? "top-[0.4rem] rotate-45" : "top-2.5",
)}
/>
</div>
<span className="sr-only">Toggle Menu</span>
</div>
<span className="flex h-8 items-center text-lg leading-none font-medium">Menu</span>
</Button>
</PopoverTrigger>
<PopoverContent
className="bg-background/90 no-scrollbar h-(--radix-popper-available-height) w-(--radix-popper-available-width) overflow-y-auto rounded-none border-none p-0 shadow-none backdrop-blur duration-100"
align="start"
side="bottom"
alignOffset={-16}
sideOffset={14}
>
<div className="flex flex-col gap-12 overflow-auto px-6 py-6">
<div className="flex flex-col gap-4">
<div className="text-muted-foreground text-sm font-medium">Menu</div>
<div className="flex flex-col gap-3">
<MobileLink href="/" onOpenChange={setOpen}>
Home
</MobileLink>
{items.map((item) => (
<MobileLink key={item.href} href={item.href} onOpenChange={setOpen}>
{item.label}
</MobileLink>
))}
</div>
</div>
</div>
</PopoverContent>
</Popover>
);
}
function MobileLink({
href,
className,
children,
onOpenChange,
...props
}: Readonly<{
href: string;
onOpenChange?: (open: boolean) => void;
children: React.ReactNode;
className?: string;
}>) {
return (
<NavLink
to={href}
onClick={() => {
onOpenChange?.(false);
}}
className={cn("text-2xl font-medium", className)}
{...props}
>
{children}
</NavLink>
);
}
+4 -4
View File
@@ -1,12 +1,12 @@
import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
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";
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx"; import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx";
import { FormSchema } from "@/types/schema.ts"; import { FormSchema } from "@/types/schema.ts";
import { PackerConfig } from "@/types/shell.ts"; import { PackerConfig } from "@/types/shell.ts";
import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
type Option = { type Option = {
name: string; name: string;
+1 -1
View File
@@ -1,6 +1,6 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
import { ScrollTextIcon } from "lucide-react"; import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
export function QuickUsage() { export function QuickUsage() {
const { t } = useTranslation(); const { t } = useTranslation();
+2 -2
View File
@@ -1,7 +1,7 @@
import { downloadBytes, formatBytes } from "@/lib/utils";
import { GenerateResult } from "@/types/shell";
import { ScrollTextIcon } from "lucide-react"; import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { downloadBytes, formatBytes } from "@/lib/utils";
import { GenerateResult } from "@/types/shell";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Separator } from "../ui/separator"; import { Separator } from "../ui/separator";
+2 -2
View File
@@ -1,3 +1,5 @@
import { FileTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { shouldHidden } from "@/lib/utils"; import { shouldHidden } from "@/lib/utils";
import { import {
AntSwordShellToolConfig, AntSwordShellToolConfig,
@@ -9,8 +11,6 @@ import {
ShellToolType, ShellToolType,
Suo5ShellToolConfig, Suo5ShellToolConfig,
} from "@/types/shell"; } from "@/types/shell";
import { FileTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { CopyableField } from "../copyable-field"; import { CopyableField } from "../copyable-field";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
import { Separator } from "../ui/separator"; import { Separator } from "../ui/separator";
+1 -1
View File
@@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next";
import { downloadBytes } from "@/lib/utils"; import { downloadBytes } from "@/lib/utils";
import { GenerateResult } from "@/types/shell"; import { GenerateResult } from "@/types/shell";
import { useTranslation } from "react-i18next";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
export function JarResult({ export function JarResult({
@@ -1,5 +1,5 @@
import { GenerateResult } from "@/types/shell";
import { TFunction } from "i18next"; import { TFunction } from "i18next";
import { GenerateResult } from "@/types/shell";
import { CodeViewer } from "../code-viewer"; import { CodeViewer } from "../code-viewer";
import { AgentResult } from "./agent"; import { AgentResult } from "./agent";
import { JarResult } from "./jar-result"; import { JarResult } from "./jar-result";
+3 -3
View File
@@ -1,12 +1,12 @@
import { DownloadIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { CodeViewer } from "@/components/code-viewer.tsx"; import { CodeViewer } from "@/components/code-viewer.tsx";
import { QuickUsage } from "@/components/quick-usage.tsx"; import { QuickUsage } from "@/components/quick-usage.tsx";
import { Button } from "@/components/ui/button.tsx"; import { Button } from "@/components/ui/button.tsx";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx";
import { downloadBytes } from "@/lib/utils.ts"; import { downloadBytes } from "@/lib/utils.ts";
import { GenerateResult } from "@/types/shell.ts"; import { GenerateResult } from "@/types/shell.ts";
import { DownloadIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { BasicInfo } from "./results/basic-info"; import { BasicInfo } from "./results/basic-info";
import { ResultComponent } from "./results/result-component"; import { ResultComponent } from "./results/result-component";
+1 -1
View File
@@ -1,6 +1,6 @@
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
import { InfoIcon } from "lucide-react"; import { InfoIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
export function JreTip() { export function JreTip() {
const { t } = useTranslation(); const { t } = useTranslation();
+5 -3
View File
@@ -1,8 +1,8 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form"; import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card"; import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form"; import { FormControl, FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { TabsContent } from "../ui/tabs"; import { TabsContent } from "../ui/tabs";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
@@ -42,7 +42,9 @@ export function AntSwordTabContent({
render={({ field }) => ( render={({ field }) => (
<FormFieldItem> <FormFieldItem>
<FormFieldLabel>{t("shellToolConfig.headerName")}</FormFieldLabel> <FormFieldLabel>{t("shellToolConfig.headerName")}</FormFieldLabel>
<Input {...field} placeholder={t("placeholders.input")} /> <FormControl>
<Input {...field} placeholder={t("placeholders.input")} />
</FormControl>
</FormFieldItem> </FormFieldItem>
)} )}
/> />
+1 -1
View File
@@ -1,6 +1,6 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form"; import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card"; import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form"; import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
+10 -8
View File
@@ -1,15 +1,17 @@
import { ChevronDown, ChevronUp, Settings } from "lucide-react";
import { Fragment, useId, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form.tsx"; import { FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form.tsx";
import { Input } from "@/components/ui/input.tsx"; import { Input } from "@/components/ui/input.tsx";
import { FormSchema } from "@/types/schema.ts"; import { FormSchema } from "@/types/schema.ts";
import { ChevronDown, ChevronUp, Settings } from "lucide-react";
import { Fragment, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<FormSchema> }>) { export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<FormSchema> }>) {
const { t } = useTranslation(); const { t } = useTranslation();
const [showAdvanced, setShowAdvanced] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false);
const shellClassNameId = useId();
const injectClassNameId = useId();
return ( return (
<Fragment> <Fragment>
<div className="pt-2"> <div className="pt-2">
@@ -32,10 +34,10 @@ export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<
name="shellClassName" name="shellClassName"
render={({ field }) => ( render={({ field }) => (
<FormFieldItem> <FormFieldItem>
<FormFieldLabel> <FormFieldLabel htmlFor={shellClassNameId}>
{t("mainConfig.shellClassName")} {t("optional")} {t("mainConfig.shellClassName")} {t("optional")}
</FormFieldLabel> </FormFieldLabel>
<Input id="shellClassName" {...field} placeholder={t("placeholders.input")} /> <Input id={shellClassNameId} {...field} placeholder={t("placeholders.input")} />
</FormFieldItem> </FormFieldItem>
)} )}
/> />
@@ -44,10 +46,10 @@ export function OptionalClassFormField({ form }: Readonly<{ form: UseFormReturn<
name="injectorClassName" name="injectorClassName"
render={({ field }) => ( render={({ field }) => (
<FormFieldItem> <FormFieldItem>
<FormFieldLabel> <FormFieldLabel htmlFor={injectClassNameId}>
{t("mainConfig.injectorClassName")} {t("optional")} {t("mainConfig.injectorClassName")} {t("optional")}
</FormFieldLabel> </FormFieldLabel>
<Input id="injectorClassName" {...field} placeholder={t("placeholders.input")} /> <Input id={injectClassNameId} {...field} placeholder={t("placeholders.input")} />
</FormFieldItem> </FormFieldItem>
)} )}
/> />
+2 -2
View File
@@ -1,8 +1,8 @@
import { env } from "@/config";
import { FormSchema } from "@/types/schema";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { FormProvider, UseFormReturn } from "react-hook-form"; import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { env } from "@/config";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card"; import { Card, CardContent } from "../ui/card";
import { FormControl, FormField, FormFieldItem, FormFieldLabel } from "../ui/form"; import { FormControl, FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
+9 -8
View File
@@ -1,9 +1,9 @@
import { t } from "i18next";
import { useId, useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import { FormSchema } from "@/types/schema"; import { FormSchema } from "@/types/schema";
import { t } from "i18next";
import { useState } from "react";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { FormControl, FormField, FormFieldItem, FormFieldLabel, FormMessage } from "../ui/form"; import { FormControl, FormField, FormFieldItem, FormFieldLabel, FormMessage } from "../ui/form";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { Label } from "../ui/label"; import { Label } from "../ui/label";
@@ -18,7 +18,8 @@ export default function CustomTabContent({
shellTypes, shellTypes,
}: Readonly<{ form: UseFormReturn<FormSchema>; shellTypes: Array<string> }>) { }: Readonly<{ form: UseFormReturn<FormSchema>; shellTypes: Array<string> }>) {
const [isFile, setIsFile] = useState(false); const [isFile, setIsFile] = useState(false);
const optionOneId = useId();
const optionTwoId = useId();
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<TabsContent value="Custom"> <TabsContent value="Custom">
@@ -43,12 +44,12 @@ export default function CustomTabContent({
className="flex items-center space-x-2" className="flex items-center space-x-2"
> >
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<RadioGroupItem value="base64" id="option-one" /> <RadioGroupItem value="base64" id={optionOneId} />
<Label htmlFor="option-one">Base64</Label> <Label htmlFor={optionOneId}>Base64</Label>
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<RadioGroupItem value="file" id="option-two" /> <RadioGroupItem value="file" id={optionTwoId} />
<Label htmlFor="option-two">File</Label> <Label htmlFor={optionTwoId}>File</Label>
</div> </div>
</RadioGroup> </RadioGroup>
<FormControl className="mt-2"> <FormControl className="mt-2">
+1 -1
View File
@@ -1,6 +1,6 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form"; import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card"; import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form"; import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
+1 -1
View File
@@ -1,6 +1,6 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form"; import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card"; import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form"; import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
+2 -3
View File
@@ -1,10 +1,9 @@
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormControl, FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form.tsx"; import { FormControl, FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form.tsx";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select.tsx";
import { FormSchema } from "@/types/schema.ts"; import { FormSchema } from "@/types/schema.ts";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
export function ShellTypeFormField({ export function ShellTypeFormField({
form, form,
shellTypes, shellTypes,
+1 -1
View File
@@ -1,6 +1,6 @@
import { FormSchema } from "@/types/schema";
import { FormProvider, UseFormReturn } from "react-hook-form"; import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FormSchema } from "@/types/schema";
import { Card, CardContent } from "../ui/card"; import { Card, CardContent } from "../ui/card";
import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form"; import { FormField, FormFieldItem, FormFieldLabel } from "../ui/form";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
@@ -1,9 +1,9 @@
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormField, FormFieldItem, FormFieldLabel, FormMessage } from "@/components/ui/form.tsx"; import { FormField, FormFieldItem, FormFieldLabel, FormMessage } from "@/components/ui/form.tsx";
import { Input } from "@/components/ui/input.tsx"; import { Input } from "@/components/ui/input.tsx";
import { shouldHidden } from "@/lib/utils"; import { shouldHidden } from "@/lib/utils";
import { FormSchema } from "@/types/schema.ts"; import { FormSchema } from "@/types/schema.ts";
import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
export function UrlPatternFormField({ form }: Readonly<{ form: UseFormReturn<FormSchema> }>) { export function UrlPatternFormField({ form }: Readonly<{ form: UseFormReturn<FormSchema> }>) {
const { t } = useTranslation(); const { t } = useTranslation();
+2 -3
View File
@@ -1,8 +1,7 @@
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
import * as React from "react"; import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) { function AlertDialog({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />; return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+36
View File
@@ -0,0 +1,36 @@
import { cva, type VariantProps } from "class-variance-authority";
import { Slot as SlotPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "span";
return <Comp data-slot="badge" className={cn(badgeVariants({ variant }), className)} {...props} />;
}
export { Badge, badgeVariants };
+92
View File
@@ -0,0 +1,92 @@
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { Slot as SlotPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
return (
<ol
data-slot="breadcrumb-list"
className={cn(
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
className,
)}
{...props}
/>
);
}
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
return <li data-slot="breadcrumb-item" className={cn("inline-flex items-center gap-1.5", className)} {...props} />;
}
function BreadcrumbLink({
asChild,
className,
...props
}: React.ComponentProps<"a"> & {
asChild?: boolean;
}) {
const Comp = asChild ? SlotPrimitive.Slot : "a";
return (
<Comp data-slot="breadcrumb-link" className={cn("hover:text-foreground transition-colors", className)} {...props} />
);
}
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-page"
role="link"
aria-disabled="true"
aria-current="page"
className={cn("text-foreground font-normal", className)}
{...props}
/>
);
}
function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">) {
return (
<li
data-slot="breadcrumb-separator"
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
);
}
function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="breadcrumb-ellipsis"
role="presentation"
aria-hidden="true"
className={cn("flex size-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
</span>
);
}
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
};
+3 -3
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { Slot as SlotPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -42,7 +42,7 @@ function Button({
VariantProps<typeof buttonVariants> & { VariantProps<typeof buttonVariants> & {
asChild?: boolean; asChild?: boolean;
}) { }) {
const Comp = asChild ? Slot : "button"; const Comp = asChild ? SlotPrimitive.Slot : "button";
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />; return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />;
} }
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon } from "lucide-react"; import { CheckIcon } from "lucide-react";
import { Checkbox as CheckboxPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+209
View File
@@ -0,0 +1,209 @@
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { ContextMenu as ContextMenuPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
function ContextMenu({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
}
function ContextMenuTrigger({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
return <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />;
}
function ContextMenuGroup({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
return <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />;
}
function ContextMenuPortal({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
return <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />;
}
function ContextMenuSub({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
}
function ContextMenuRadioGroup({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
return <ContextMenuPrimitive.RadioGroup data-slot="context-menu-radio-group" {...props} />;
}
function ContextMenuSubTrigger({
className,
inset,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
<ContextMenuPrimitive.SubTrigger
data-slot="context-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto" />
</ContextMenuPrimitive.SubTrigger>
);
}
function ContextMenuSubContent({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
return (
<ContextMenuPrimitive.SubContent
data-slot="context-menu-sub-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className,
)}
{...props}
/>
);
}
function ContextMenuContent({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
return (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
data-slot="context-menu-content"
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className,
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
);
}
function ContextMenuItem({
className,
inset,
variant = "default",
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
}) {
return (
<ContextMenuPrimitive.Item
data-slot="context-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function ContextMenuCheckboxItem({
className,
children,
checked,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
return (
<ContextMenuPrimitive.CheckboxItem
data-slot="context-menu-checkbox-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
);
}
function ContextMenuRadioItem({
className,
children,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
return (
<ContextMenuPrimitive.RadioItem
data-slot="context-menu-radio-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
);
}
function ContextMenuLabel({
className,
inset,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
<ContextMenuPrimitive.Label
data-slot="context-menu-label"
data-inset={inset}
className={cn("text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
{...props}
/>
);
}
function ContextMenuSeparator({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
return (
<ContextMenuPrimitive.Separator
data-slot="context-menu-separator"
className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function ContextMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="context-menu-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...props}
/>
);
}
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
};
+121
View File
@@ -0,0 +1,121 @@
import { XIcon } from "lucide-react";
import { Dialog as DialogPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
className,
)}
{...props}
/>
);
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
);
}
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
{...props}
/>
);
}
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
);
}
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+7 -9
View File
@@ -1,18 +1,16 @@
import { Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui";
import * as React from "react"; import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import { import {
Controller, Controller,
FormProvider,
useFormContext,
useFormState,
type ControllerProps, type ControllerProps,
type FieldPath, type FieldPath,
type FieldValues, type FieldValues,
FormProvider,
useFormContext,
useFormState,
} from "react-hook-form"; } from "react-hook-form";
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
const Form = FormProvider; const Form = FormProvider;
@@ -115,11 +113,11 @@ function FormFieldLabel({ className, ...props }: React.ComponentProps<typeof Lab
); );
} }
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) { function FormControl({ ...props }: React.ComponentProps<typeof SlotPrimitive.Slot>) {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField(); const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return ( return (
<Slot <SlotPrimitive.Slot
data-slot="form-control" data-slot="form-control"
id={formItemId} id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`} aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
+1 -1
View File
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type} type={type}
data-slot="input" data-slot="input"
className={cn( className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-8 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-8 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className, className,
+1 -1
View File
@@ -1,5 +1,5 @@
import { Label as LabelPrimitive } from "radix-ui";
import * as React from "react"; import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+142
View File
@@ -0,0 +1,142 @@
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import { cva } from "class-variance-authority";
import { ChevronDownIcon } from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
function NavigationMenu({
className,
children,
viewport = true,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
viewport?: boolean;
}) {
return (
<NavigationMenuPrimitive.Root
data-slot="navigation-menu"
data-viewport={viewport}
className={cn("group/navigation-menu relative flex max-w-max flex-1 items-center justify-center", className)}
{...props}
>
{children}
{viewport && <NavigationMenuViewport />}
</NavigationMenuPrimitive.Root>
);
}
function NavigationMenuList({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
data-slot="navigation-menu-list"
className={cn("group flex flex-1 list-none items-center justify-center gap-1", className)}
{...props}
/>
);
}
function NavigationMenuItem({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return (
<NavigationMenuPrimitive.Item data-slot="navigation-menu-item" className={cn("relative", className)} {...props} />
);
}
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1",
);
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
data-slot="navigation-menu-trigger"
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
);
}
function NavigationMenuContent({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
className,
)}
{...props}
/>
);
}
function NavigationMenuViewport({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return (
<div className={cn("absolute top-full left-0 isolate z-50 flex justify-center")}>
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
className,
)}
{...props}
/>
</div>
);
}
function NavigationMenuLink({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return (
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
<NavigationMenuPrimitive.Indicator
data-slot="navigation-menu-indicator"
className={cn(
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
className,
)}
{...props}
>
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
</NavigationMenuPrimitive.Indicator>
);
}
export {
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
navigationMenuTriggerStyle,
};
+40
View File
@@ -0,0 +1,40 @@
import * as PopoverPrimitive from "@radix-ui/react-popover";
import * as React from "react";
import { cn } from "@/lib/utils";
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />;
}
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}
function PopoverContent({
className,
align = "center",
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
);
}
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
import { CircleIcon } from "lucide-react"; import { CircleIcon } from "lucide-react";
import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+46
View File
@@ -0,0 +1,46 @@
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils";
function ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...props}>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
);
}
function ScrollBar({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
);
}
export { ScrollArea, ScrollBar };
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { Select as SelectPrimitive } from "radix-ui";
import * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Separator as SeparatorPrimitive } from "radix-ui";
import * as React from "react"; import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Switch as SwitchPrimitive } from "radix-ui";
import * as React from "react"; import * as React from "react";
import * as SwitchPrimitive from "@radix-ui/react-switch";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+73
View File
@@ -0,0 +1,73 @@
import * as React from "react";
import { cn } from "@/lib/utils";
function Table({ className, ...props }: React.ComponentProps<"table">) {
return (
<div data-slot="table-container" className="relative w-full overflow-x-auto">
<table data-slot="table" className={cn("w-full caption-bottom text-sm", className)} {...props} />
</div>
);
}
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />;
}
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />;
}
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return (
<tfoot
data-slot="table-footer"
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
{...props}
/>
);
}
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
return (
<tr
data-slot="table-row"
className={cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className)}
{...props}
/>
);
}
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
return (
<th
data-slot="table-head"
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
);
}
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
return (
<td
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props}
/>
);
}
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
return (
<caption data-slot="table-caption" className={cn("text-muted-foreground mt-4 text-sm", className)} {...props} />
);
}
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
+1 -1
View File
@@ -1,5 +1,5 @@
import { Tabs as TabsPrimitive } from "radix-ui";
import * as React from "react"; import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Tooltip as TooltipPrimitive } from "radix-ui";
import * as React from "react"; import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,8 +1,8 @@
import { env } from "@/config";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { CircleX, LoaderCircle, RefreshCcw } from "lucide-react"; import { CircleX, LoaderCircle, RefreshCcw } from "lucide-react";
import type React from "react"; import type React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { env } from "@/config";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
+33
View File
@@ -45,6 +45,11 @@
--sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32); --sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067); --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 { .dark {
@@ -79,6 +84,11 @@
--sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%); --sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938); --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 { @theme inline {
@@ -118,6 +128,29 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border); --color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring); --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 { @layer base {
+15
View File
@@ -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",
},
],
};
+2 -2
View File
@@ -1,9 +1,9 @@
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import "./index.css"; import "./index.css";
import { TailwindIndicator } from "@/components/tailwind-indicator.tsx";
import { env } from "@/config.ts";
import { I18nextProvider } from "react-i18next"; import { I18nextProvider } from "react-i18next";
import { RouterProvider } from "react-router-dom"; import { RouterProvider } from "react-router-dom";
import { TailwindIndicator } from "@/components/tailwind-indicator.tsx";
import { env } from "@/config.ts";
import i18n from "./i18n/i18n"; import i18n from "./i18n/i18n";
import { QueryProvider } from "./providers/query-client-provider"; import { QueryProvider } from "./providers/query-client-provider";
import { router } from "./router"; import { router } from "./router";
+802
View File
@@ -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<PayloadFormValues>({
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 (
<FormField
control={form.control}
name="echoContent"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel></FormFieldLabel>
<Select onValueChange={field.onChange} defaultValue={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>
)}
/>
);
};
const renderDynamicFields = () => {
// --- Response Header / Body ---
if ((watchedEchoMethod === "header" || watchedEchoMethod === "body") && watchedEchoContent) {
return (
<>
{watchedEchoContent === "bytecode" && (
<div className="space-y-4 pt-4 border-t mt-4">
<FormField
control={form.control}
name="paramSource"
render={({ field }) => (
<FormFieldItem className="space-y-2">
<FormFieldLabel></FormFieldLabel>
<FormControl>
<RadioGroup onValueChange={field.onChange} defaultValue={field.value} className="flex gap-4">
<FormItem className="flex items-center space-x-2">
<FormControl>
<RadioGroupItem value="builtin" />
</FormControl>
<FormLabel className="font-normal"></FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-2">
<FormControl>
<RadioGroupItem value="requestHeader" />
</FormControl>
<FormLabel className="font-normal"> Request Header </FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-2">
<FormControl>
<RadioGroupItem value="requestParam" />
</FormControl>
<FormLabel className="font-normal"> Request Param </FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
{watchedParamSource === "builtin" && (
<FormField
control={form.control}
name="bytecodeBase64"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel> Base64</FormFieldLabel>
<RadioGroup
value={isFile ? "file" : "base64"}
onValueChange={(value) => {
field.onChange("");
setIsFile(value === "file");
}}
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>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="file" id={optionTwoId} />
<Label htmlFor={optionTwoId}>File</Label>
</div>
</RadioGroup>
<FormControl className="mt-2 items-center">
{isFile ? (
<Input
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
const base64String = (event.target?.result as string)?.split(",")[1] || "";
field.onChange(base64String);
};
reader.readAsDataURL(file);
}
}}
accept=".class"
placeholder="请输入"
type="file"
/>
) : (
<Textarea {...field} placeholder="请输入" className="h-24" />
)}
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
)}
{watchedParamSource === "requestParam" && (
<FormField
control={form.control}
name="requestParamName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>Request Param Name</FormFieldLabel>
<FormControl>
<Input placeholder="例如: cmd, data, ..." {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
)}
{watchedParamSource === "requestHeader" && (
<FormField
control={form.control}
name="requestHeaderName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>Request Header Name</FormFieldLabel>
<FormControl>
<Input placeholder="例如: cmd, data, ..." {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
)}
</div>
)}
{watchedEchoContent === "command" && (
<div className="space-y-4 pt-4 border-t mt-4">
<FormField
control={form.control}
name="paramSource"
render={({ field }) => (
<FormFieldItem className="space-y-2">
<FormFieldLabel></FormFieldLabel>
<FormControl>
<RadioGroup onValueChange={field.onChange} defaultValue={field.value} className="flex gap-4">
<FormItem className="flex items-center space-x-2">
<FormControl>
<RadioGroupItem value="builtin" />
</FormControl>
<FormLabel className="font-normal"></FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-2">
<FormControl>
<RadioGroupItem value="requestHeader" />
</FormControl>
<FormLabel className="font-normal"> Request Header </FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-2">
<FormControl>
<RadioGroupItem value="requestParam" />
</FormControl>
<FormLabel className="font-normal"> Request Param </FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
{watchedParamSource === "builtin" && (
<FormField
control={form.control}
name="command"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel></FormFieldLabel>
<FormControl>
<Input placeholder="例如: whoami, id, hostname" {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
)}
{watchedParamSource === "requestParam" && (
<FormField
control={form.control}
name="requestParamName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>Request Param Name</FormFieldLabel>
<FormControl>
<Input placeholder="例如: cmd, data, ..." {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
)}
{watchedParamSource === "requestHeader" && (
<FormField
control={form.control}
name="requestHeaderName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>Request Header Name</FormFieldLabel>
<FormControl>
<Input placeholder="例如: cmd, data, ..." {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
)}
</div>
)}
</>
);
}
// --- Sleep Delay ---
if (watchedEchoMethod === "sleep" && watchedEchoContent) {
return (
<div className="space-y-4 pt-4 border-t mt-4">
{watchedEchoContent === "middleware" && (
<div className="space-y-4">
<FormField
control={form.control}
name="sleepMiddleware"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel></FormFieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="选择中间件..." />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="tomcat">Tomcat</SelectItem>
<SelectItem value="jetty">Jetty</SelectItem>
<SelectItem value="springboot">Spring Boot</SelectItem>
<SelectItem value="weblogic">WebLogic</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
<FormField
control={form.control}
name="sleepMiddlewareDelay"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel> ()</FormFieldLabel>
<FormControl>
<Input
type="number"
placeholder="例如: 5"
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
</div>
)}
{watchedEchoContent === "os" && (
<div className="space-y-4">
<FormFieldLabel></FormFieldLabel>
{fields.map((item, index) => (
<div key={item.id} className="flex items-center gap-2">
<Controller
control={form.control}
name={`sleepOsDelays.${index}.os`}
render={({ field }) => (
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="linux">Linux</SelectItem>
<SelectItem value="windows">Windows</SelectItem>
<SelectItem value="macos">macOS</SelectItem>
</SelectContent>
</Select>
)}
/>
<Controller
control={form.control}
name={`sleepOsDelays.${index}.delay`}
render={({ field }) => (
<Input
type="number"
placeholder="延迟(秒)"
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
)}
/>
<Button type="button" variant="destructive" size="icon" onClick={() => remove(index)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
<Button type="button" variant="outline" size="sm" onClick={() => append({ os: "windows", delay: 10 })}>
</Button>
<FormMessage>{form.formState.errors.sleepOsDelays?.message}</FormMessage>
</div>
)}
{watchedEchoContent === "jdk" && (
<div className="space-y-4">
<FormField
control={form.control}
name="sleepJdkDetectionType"
render={({ field }) => (
<FormFieldItem className="space-y-2">
<FormFieldLabel>JDK </FormFieldLabel>
<FormControl>
<RadioGroup onValueChange={field.onChange} defaultValue={field.value} className="flex gap-4">
<FormItem className="flex items-center space-x-2">
<FormControl>
<RadioGroupItem value="version" />
</FormControl>
<FormLabel className="font-normal"> ()</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-2">
<FormControl>
<RadioGroupItem value="type" />
</FormControl>
<FormLabel className="font-normal"> (JDK/JRE)</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
</FormFieldItem>
)}
/>
{watchedSleepJdkDetectionType === "version" && (
<FormField
control={form.control}
name="sleepJdkVersion"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>JDK </FormFieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="8">JDK 8</SelectItem>
<SelectItem value="11">JDK 11</SelectItem>
<SelectItem value="17">JDK 17</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
)}
{watchedSleepJdkDetectionType === "type" && (
<FormField
control={form.control}
name="sleepJdkType"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel></FormFieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="jdk">JDK</SelectItem>
<SelectItem value="jre">JRE</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
)}
<FormField
control={form.control}
name="sleepJdkDelay"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel> ()</FormFieldLabel>
<FormControl>
<Input
type="number"
placeholder="例如: 5"
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
</div>
)}
{watchedEchoContent === "bytecode" && (
<FormField
control={form.control}
name="sleepBytecodeSuccessDelay"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel> ()</FormFieldLabel>
<FormControl>
<Input
type="number"
placeholder="例如: 8"
{...field}
onChange={(event) => field.onChange(+event.target.value)}
/>
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
)}
</div>
);
}
return null;
};
return (
<div className="max-w-4xl mx-auto p-6 space-y-6">
<Card>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<CardContent className="mt-2">
<FormField
control={form.control}
name="echoMethod"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel></FormFieldLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="header">Response Header</SelectItem>
<SelectItem value="body">Response Body</SelectItem>
<SelectItem value="dnslog">DNSLog</SelectItem>
<SelectItem value="httplog">HTTPLog</SelectItem>
<SelectItem value="sleep">Sleep </SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
{/* --- 根据回显方式出现的特定字段 --- */}
{(watchedEchoMethod === "header" || watchedEchoMethod === "body") && (
<FormField
control={form.control}
name="middlewareType"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel></FormFieldLabel>
<FormDescription> Request/Response </FormDescription>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="选择中间件..." />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="tomcat">Tomcat</SelectItem>
<SelectItem value="jetty">Jetty</SelectItem>
<SelectItem value="springboot">Spring Boot</SelectItem>
<SelectItem value="weblogic">WebLogic</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormFieldItem>
)}
/>
)}
{watchedEchoMethod === "header" && (
<FormField
control={form.control}
name="headerName"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>Response Header Name</FormFieldLabel>
<FormControl>
<Input placeholder="例如: X-Echo-Result" {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
)}
{(watchedEchoMethod === "dnslog" || watchedEchoMethod === "httplog") && (
<>
<Alert className="mt-2 mb-2">
<AlertTriangle className="h-4 w-4" />
<AlertTitle></AlertTitle>
<AlertDescription>访 ()</AlertDescription>
</Alert>
<FormField
control={form.control}
name="logAddress"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{watchedEchoMethod === "dnslog" ? "DNSLog" : "HTTPLog"} </FormFieldLabel>
<FormControl>
<Input placeholder="例如: abcde.dnslog.cn" {...field} />
</FormControl>
<FormMessage />
</FormFieldItem>
)}
/>
</>
)}
{watchedEchoMethod && renderContentOptions()}
{renderDynamicFields()}
</CardContent>
<CardFooter className="mt-2">
<Button type="submit"></Button>
</CardFooter>
</form>
</Form>
</Card>
</div>
);
}
+296
View File
@@ -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: <ShieldCheckIcon className="w-8 h-8 text-green-400" />,
title: "Non-Intrusive",
description:
"Generated memshells don't interfere with normal middleware traffic, even with multiple shells injected.",
},
{
icon: <ZapIcon className="w-8 h-8 text-yellow-400" />,
title: "High Availability",
description: "Comes with comprehensive CI integration tests, covering mainstream middleware for reliability.",
},
{
icon: <MinimizeIcon className="w-8 h-8 text-blue-400" />,
title: "Minimal Size",
description: "The core code of the memshell is kept as concise as possible for efficient transfer and injection.",
},
{
icon: <CombineIcon className="w-8 h-8 text-purple-400" />,
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 (
<div className="min-h-screen font-sans bg-background text-foreground">
<section className="relative text-center py-20 overflow-hidden">
<div className="absolute top-0 left-0 w-full h-full bg-grid-black/[0.05] dark:bg-grid-white/[0.05] [mask-image:linear-gradient(to_bottom,white_10%,transparent_100%)]" />
<div className="container mx-auto px-4 relative z-10">
<motion.div initial={{ opacity: 0, y: -50 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8 }}>
<div className="inline-flex items-center px-4 py-2 rounded-full border sm:h-8 mb-8 transition-colors dark:bg-red-900/20 dark:border-red-800 dark:text-red-300 bg-red-50 border-red-200 text-red-700">
<Shield className="w-4 h-4 mr-2" />
<span className="text-sm">For Security Research & Authorized Testing Only</span>
</div>
<h1 className="text-5xl md:text-7xl font-bold mb-8">
<span className="dark:text-white text-gray-900">
MemShell
<LineShadowText className="italic" shadowColor={shadowColor}>
Party
</LineShadowText>
</span>
</h1>
<p className="text-xl md:text-2xl mb-12 max-w-4xl mx-auto leading-relaxed text-muted-foreground">
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.
</p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.3 }}
className="mt-8 flex justify-center gap-4 flex-wrap"
>
<NavLink to="/memshell">
<Button size="lg">
<TerminalIcon className="mr-2 h-5 w-5" />
Get Started
</Button>
</NavLink>
<a href="https://github.com/ReaJason/MemShellParty" target="_blank" rel="noopener noreferrer">
<Button size="lg" variant="outline">
<GithubIcon className="mr-2 h-5 w-5" />
View on GitHub
</Button>
</a>
</motion.div>
</div>
</section>
<section className="py-20 bg-secondary/30">
<div className="container mx-auto px-4">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold tracking-tight">Why Choose MemShellParty?</h2>
<p className="mt-3 text-lg text-muted-foreground">Designed for modern offensive and defensive scenarios.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{features.map((feature, index) => (
<motion.div
key={feature.title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.5 }}
transition={{ delay: index * 0.1 }}
>
<Card className="h-full border-border/60 bg-card">
<CardHeader className="flex flex-row items-center gap-4">
{feature.icon}
<CardTitle className="text-lg">{feature.title}</CardTitle>
</CardHeader>
<CardContent>
<CardDescription>{feature.description}</CardDescription>
</CardContent>
</Card>
</motion.div>
))}
</div>
</div>
</section>
<section className="py-20">
<div className="container mx-auto px-4">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold tracking-tight">Extensive Support Matrix</h2>
<p className="mt-3 text-lg text-muted-foreground">
Compatible with a wide range of industry-standard technologies.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
<motion.div
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, amount: 0.5 }}
transition={{ duration: 0.5 }}
>
<Card className="h-full">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<ServerIcon className="w-6 h-6" />
Supported Middleware
</CardTitle>
</CardHeader>
<CardContent className="flex flex-wrap gap-2 mt-2">
{supportedMiddleware.map((tech) => (
<Badge key={tech} variant="secondary" className="text-base py-1 px-3">
{tech}
</Badge>
))}
</CardContent>
</Card>
</motion.div>
<motion.div
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, amount: 0.5 }}
transition={{ duration: 0.5 }}
>
<Card className="h-full">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<ShellIcon className="w-6 h-6" />
Supported Shell Tools
</CardTitle>
</CardHeader>
<CardContent className="flex flex-wrap gap-2 mt-2">
{supportedShells.map((shell) => (
<Badge key={shell} variant="secondary" className="text-base py-1 px-3">
{shell}
</Badge>
))}
</CardContent>
</Card>
</motion.div>
</div>
</div>
</section>
<section className="py-20">
<div className="container mx-auto px-4">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold tracking-tight">Get Your Shell in 3 Simple Steps</h2>
<p className="mt-3 text-lg text-muted-foreground">A streamlined process from selection to generation.</p>
</div>
<div className="max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-8 text-center">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.5 }}
transition={{ delay: 0.1 }}
className="flex flex-col items-center"
>
<div className="flex items-center justify-center w-16 h-16 rounded-full bg-primary text-primary-foreground text-2xl font-bold mb-4">
1
</div>
<h3 className="text-xl font-semibold mb-2">Select Middleware</h3>
<p className="text-muted-foreground">Choose your target environment like Tomcat or Spring.</p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.5 }}
transition={{ delay: 0.2 }}
className="flex flex-col items-center"
>
<div className="flex items-center justify-center w-16 h-16 rounded-full bg-primary text-primary-foreground text-2xl font-bold mb-4">
2
</div>
<h3 className="text-xl font-semibold mb-2">Configure Shell</h3>
<p className="text-muted-foreground">Pick a shell type and set parameters like passwords or keys.</p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.5 }}
transition={{ delay: 0.3 }}
className="flex flex-col items-center"
>
<div className="flex items-center justify-center w-16 h-16 rounded-full bg-primary text-primary-foreground text-2xl font-bold mb-4">
3
</div>
<h3 className="text-xl font-semibold mb-2">Generate Payload</h3>
<p className="text-muted-foreground">Click generate to get the final payload for your operation.</p>
</motion.div>
</div>
</div>
</section>
{/* --- Final CTA Section --- */}
<section className="py-20 text-center bg-secondary/30">
<div className="container mx-auto px-4">
<h2 className="text-3xl md:text-4xl font-bold tracking-tight">Ready to Join the Party?</h2>
<p className="mt-4 text-lg text-muted-foreground max-w-xl mx-auto">
Visit the GitHub repository to start your memshell exploration, or contribute to the project!
</p>
<div className="mt-8">
<a href="https://github.com/ReaJason/MemShellParty" target="_blank" rel="noopener noreferrer">
<RainbowButton size="lg">
<GithubIcon className="mr-2 h-5 w-5" />
Star & Fork on GitHub
</RainbowButton>
</a>
</div>
</div>
</section>
{/* --- Footer --- */}
<footer className="border-t py-8">
<div className="container mx-auto px-4 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between text-center sm:text-left">
<div>
<p className="text-sm font-semibold">MemShellParty</p>
<p className="text-xs">
Built with by{" "}
<a href="https://reajason.eu.org" rel="noreferrer noopener" target="_blank" className="font-medium">
ReaJason
</a>
</p>
</div>
</div>
</footer>
</div>
);
}
@@ -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 { MainConfigCard } from "@/components/main-config-card.tsx";
import { PackageConfigCard } from "@/components/package-config-card.tsx"; import { PackageConfigCard } from "@/components/package-config-card.tsx";
import { ShellResult } from "@/components/shell-result.tsx"; import { ShellResult } from "@/components/shell-result.tsx";
@@ -15,15 +22,8 @@ import {
ShellToolType, ShellToolType,
} from "@/types/shell.ts"; } from "@/types/shell.ts";
import { transformToPostData } from "@/utils/transformer.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 urlParams = useLoaderData();
const { data: serverConfig } = useQuery<ServerConfig>({ const { data: serverConfig } = useQuery<ServerConfig>({
+13 -3
View File
@@ -1,8 +1,10 @@
import { createBrowserRouter } from "react-router-dom";
import RootLayout from "@/components/layouts/root-layout"; import RootLayout from "@/components/layouts/root-layout";
import { env } from "@/config"; import { env } from "@/config";
import IndexPage from "@/pages"; import MemShellPage from "@/pages/memshell";
import { FormSchema } from "@/types/schema"; 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 // Function to parse URL parameters into form default values
const parseUrlParams = (searchParams: URLSearchParams): Partial<FormSchema> => { const parseUrlParams = (searchParams: URLSearchParams): Partial<FormSchema> => {
@@ -48,12 +50,20 @@ export const router = createBrowserRouter(
children: [ children: [
{ {
index: true, index: true,
element: <IndexPage />, element: <MemShellPartyLandingPage />,
},
{
path: "memshell",
element: <MemShellPage />,
loader: ({ request }) => { loader: ({ request }) => {
const url = new URL(request.url); const url = new URL(request.url);
return parseUrlParams(url.searchParams); return parseUrlParams(url.searchParams);
}, },
}, },
{
path: "detection",
element: <DetectionPage />,
},
], ],
}, },
], ],
+6 -7
View File
@@ -80,15 +80,14 @@ export const useYupValidationResolver = (validationSchema: yup.ObjectSchema<any>
return { return {
values: {} as FormSchema, values: {} as FormSchema,
errors: errors.inner.reduce( errors: errors.inner.reduce(
(allErrors, currentError) => ({ (allErrors, currentError) => {
// biome-ignore lint/performance/noAccumulatingSpread: <explanation> allErrors[currentError.path as keyof FormSchema] = {
...allErrors,
[currentError.path as keyof FormSchema]: {
type: currentError.type ?? "validation", type: currentError.type ?? "validation",
message: currentError.message, message: currentError.message,
}, };
}), return allErrors;
{}, },
{} as FieldErrors<FormSchema>,
), ),
}; };
} }