feat: support fumadocs

This commit is contained in:
ReaJason
2025-12-08 01:43:41 +08:00
parent 8b71ca9911
commit ca1d567ce7
140 changed files with 4788 additions and 1438 deletions
@@ -17,7 +17,7 @@ import java.util.Base64;
@CrossOrigin("*") @CrossOrigin("*")
public class ClassNameParseController { public class ClassNameParseController {
@PostMapping("/className") @PostMapping("/api/className")
public String className(@RequestBody String classBase64) { public String className(@RequestBody String classBase64) {
return ClassNameReader.getClassName(new ClassReader(Base64.getDecoder().decode(classBase64))); return ClassNameReader.getClassName(new ClassReader(Base64.getDecoder().decode(classBase64)));
} }
@@ -17,7 +17,7 @@ import java.util.*;
* @since 2024/12/13 * @since 2024/12/13
*/ */
@RestController @RestController
@RequestMapping("/config") @RequestMapping("/api/config")
@CrossOrigin("*") @CrossOrigin("*")
public class ConfigController { public class ConfigController {
@@ -19,7 +19,7 @@ import java.util.Base64;
* @since 2024/12/18 * @since 2024/12/18
*/ */
@RestController @RestController
@RequestMapping("/memshell/generate") @RequestMapping("/api/memshell/generate")
@CrossOrigin("*") @CrossOrigin("*")
public class MemShellGeneratorController { public class MemShellGeneratorController {
@PostMapping @PostMapping
@@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.*;
* @since 2025/8/10 * @since 2025/8/10
*/ */
@RestController @RestController
@RequestMapping("/probe/generate") @RequestMapping("/api/probe/generate")
@CrossOrigin("*") @CrossOrigin("*")
public class ProbeShellGeneratorController { public class ProbeShellGeneratorController {
@PostMapping @PostMapping
@@ -22,7 +22,7 @@ import java.util.Map;
*/ */
@RestController @RestController
@CrossOrigin("*") @CrossOrigin("*")
@RequestMapping("/version") @RequestMapping("/api/version")
public class VersionController { public class VersionController {
@Value("${spring.application.version}") @Value("${spring.application.version}")
@@ -1,7 +1,17 @@
package com.reajason.javaweb.boot.controller; package com.reajason.javaweb.boot.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/** /**
* @author ReaJason * @author ReaJason
@@ -11,6 +21,51 @@ import org.springframework.web.bind.annotation.GetMapping;
public class ViewController { public class ViewController {
@GetMapping("/") @GetMapping("/")
public String index(){ public String index(){
return "index"; return "redirect:/ui";
}
@GetMapping({"/api/search", "/api/search.data"})
@ResponseBody
public String handleSearch(HttpServletRequest request, HttpServletResponse response) {
String fullPath = request.getRequestURI();
String relativePath = fullPath.substring(1);
return renderFileData(relativePath, response);
}
@GetMapping({"/ui/docs/*.data", "/ui/*.data"})
@ResponseBody
public String handleDataFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
String fullPath = request.getRequestURI();
String relativePath = fullPath.substring(4);
return renderFileData(relativePath, response);
}
@GetMapping("/ui/**")
public String handleHtmlView(HttpServletRequest request) {
String fullPath = request.getRequestURI();
String viewPath = fullPath.substring(3);
return viewPath + "/index";
}
private String renderFileData(String relativePath, HttpServletResponse response) {
try {
String templatePath = "templates/" + relativePath;
ClassPathResource resource = new ClassPathResource(templatePath);
if (!resource.exists()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return "File not found: " + relativePath;
}
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
response.setCharacterEncoding("UTF-8");
InputStreamReader reader = new InputStreamReader(
resource.getInputStream(),
StandardCharsets.UTF_8
);
return FileCopyUtils.copyToString(reader);
} catch (IOException e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return "Error reading file: " + e.getMessage();
}
} }
} }
+3
View File
@@ -2,3 +2,6 @@ spring:
application: application:
name: boot name: boot
version: ${version} version: ${version}
mvc:
pathmatch:
matching-strategy: ant_path_matcher
+1 -1
View File
@@ -1,2 +1,2 @@
VITE_APP_API_URL=http://127.0.0.1:8080 VITE_APP_API_URL=http://127.0.0.1:8889
VITE_APP_BASE_PATH=/ VITE_APP_BASE_PATH=/
+1 -1
View File
@@ -1,2 +1,2 @@
VITE_APP_API_URL= VITE_APP_API_URL=
VITE_APP_BASE_PATH=/ VITE_APP_BASE_PATH=/ui
+5 -18
View File
@@ -1,20 +1,7 @@
# Local
.DS_Store .DS_Store
*.local /node_modules/
*.log*
# Dist # React Router
node_modules /.react-router/
dist/ /build/
.vinxi .source
.output
.vercel
.netlify
.wrangler
# IDE
.vscode/*
!.vscode/extensions.json
.idea
tsconfig.app.tsbuildinfo
tsconfig.node.tsbuildinfo
+2 -43
View File
@@ -1,16 +1,7 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tw-animate-css"; @import "tw-animate-css";
@import "fumadocs-ui/css/neutral.css";
@source "../../../apps/**/*.{ts,tsx}"; @import "fumadocs-ui/css/preset.css";
@source "../../../components/**/*.{ts,tsx}";
@source "../**/*.{ts,tsx}";
@custom-variant dark (&:is(.dark *));
@theme {
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
:root { :root {
--radius: 0.625rem; --radius: 0.625rem;
@@ -152,35 +143,3 @@
} }
} }
} }
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
html,
body {
overflow: hidden;
height: 100%;
margin: 0;
}
}
.tabs-list {
scrollbar-width: thin;
}
.tabs-list::-webkit-scrollbar {
@apply h-1.5 w-1.5;
}
.tabs-list::-webkit-scrollbar-track {
@apply bg-transparent;
}
.tabs-list::-webkit-scrollbar-thumb {
@apply bg-muted-foreground/20 hover:bg-muted-foreground/30 rounded-full;
}
@@ -7,11 +7,11 @@ import {
useEffect, useEffect,
useState, useState,
} from "react"; } 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 { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"; import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
import java from "react-syntax-highlighter/dist/esm/languages/prism/java"; import java from "react-syntax-highlighter/dist/esm/languages/prism/java";
import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import materialDark from "react-syntax-highlighter/dist/esm/styles/prism/material-dark";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button, type buttonVariants } from "@/components/ui/button"; import { Button, type buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -26,7 +26,7 @@ export function CopyButton({
value, value,
}: Readonly<CopyButtonProps & VariantProps<typeof buttonVariants>>) { }: Readonly<CopyButtonProps & VariantProps<typeof buttonVariants>>) {
const [hasCopied, setHasCopied] = useState(false); const [hasCopied, setHasCopied] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation(["common"]);
useEffect(() => { useEffect(() => {
if (hasCopied) { if (hasCopied) {
@@ -45,7 +45,7 @@ export function CopyButton({
}, [hasCopied, t]); }, [hasCopied, t]);
return ( return (
<CopyToClipboard text={value} onCopy={handleCopy}> <CopyToClipboard.CopyToClipboard text={value} onCopy={handleCopy}>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
@@ -55,7 +55,7 @@ export function CopyButton({
> >
{hasCopied ? <Check /> : <Copy />} {hasCopied ? <Check /> : <Copy />}
</Button> </Button>
</CopyToClipboard> </CopyToClipboard.CopyToClipboard>
); );
} }
@@ -1,6 +1,6 @@
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 { Button } from "@/components/ui/button";
@@ -18,7 +18,7 @@ export function CopyableField({
text, text,
}: Readonly<CopyableFieldProps>) { }: Readonly<CopyableFieldProps>) {
const [hasCopied, setHasCopied] = useState(false); const [hasCopied, setHasCopied] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation(["common"]);
useEffect(() => { useEffect(() => {
if (hasCopied) { if (hasCopied) {
@@ -43,7 +43,7 @@ export function CopyableField({
<div className="flex items-center justify-between gap-2 h-6"> <div className="flex items-center justify-between gap-2 h-6">
<Label className="text-sm text-muted-foreground">{label}</Label> <Label className="text-sm text-muted-foreground">{label}</Label>
{value && ( {value && (
<CopyToClipboard text={value} onCopy={handleCopy}> <CopyToClipboard.CopyToClipboard text={value} onCopy={handleCopy}>
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
@@ -57,7 +57,7 @@ export function CopyableField({
<Copy className="h-4 w-4" /> <Copy className="h-4 w-4" />
)} )}
</Button> </Button>
</CopyToClipboard> </CopyToClipboard.CopyToClipboard>
)} )}
</div> </div>
<p className="text-sm break-all">{text}</p> <p className="text-sm break-all">{text}</p>
@@ -19,12 +19,7 @@ import CustomTabContent from "@/components/memshell/tabs/custom-tab";
import { GodzillaTabContent } from "@/components/memshell/tabs/godzilla-tab"; import { GodzillaTabContent } from "@/components/memshell/tabs/godzilla-tab";
import { NeoRegTabContent } from "@/components/memshell/tabs/neoreg-tab"; import { NeoRegTabContent } from "@/components/memshell/tabs/neoreg-tab";
import { Suo5TabContent } from "@/components/memshell/tabs/suo5-tab"; import { Suo5TabContent } from "@/components/memshell/tabs/suo5-tab";
import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card.tsx";
import { import {
FormControl, FormControl,
FormDescription, FormDescription,
@@ -34,16 +29,16 @@ import {
FormItem, FormItem,
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form.tsx"; } from "@/components/ui/form";
import { Label } from "@/components/ui/label.tsx"; import { Label } from "@/components/ui/label";
import { import {
Select, Select,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select.tsx"; } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch.tsx"; import { Switch } from "@/components/ui/switch";
import { Tabs } from "@/components/ui/tabs"; import { Tabs } from "@/components/ui/tabs";
import { import {
type MainConfig, type MainConfig,
@@ -332,7 +327,38 @@ export default function MainConfigCard({
)} )}
/> />
</div> </div>
<div className="flex gap-4 mt-4 flex-col sm:flex-row"> <div className="grid grid-cols-1">
<FormField
control={form.control}
name="shellTool"
render={({ field }) => (
<FormFieldItem>
<FormFieldLabel>{t("common:shellTool")}</FormFieldLabel>
<Select
value={field.value}
onValueChange={(v) => handleShellToolChange(v)}
>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
{shellTools.map((tool) => (
<SelectItem key={tool} value={tool}>
<span className="flex items-center gap-2">
{shellToolIcons[tool]}
{tool}
</span>
</SelectItem>
))}
</SelectContent>
</Select>
</FormFieldItem>
)}
/>
</div>
<div className="flex gap-4 mt-4 flex-col sm:flex-row xl:grid xl:grid-cols-2 2xl:flex 2xl:flex-row">
<FormField <FormField
control={form.control} control={form.control}
name="debug" name="debug"
@@ -403,35 +429,6 @@ export default function MainConfigCard({
</CardContent> </CardContent>
</Card> </Card>
<Tabs value={shellTool} className="w-full"> <Tabs value={shellTool} className="w-full">
<FormField
control={form.control}
name="shellTool"
render={({ field }) => (
<FormItem>
<Select
value={field.value}
onValueChange={(v) => handleShellToolChange(v)}
>
<FormControl>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
</FormControl>
<SelectContent>
{shellTools.map((tool) => (
<SelectItem key={tool} value={tool}>
<span className="flex items-center gap-2">
{shellToolIcons[tool]}
{tool}
</span>
</SelectItem>
))}
</SelectContent>
</Select>
</FormItem>
)}
/>
<GodzillaTabContent form={form} shellTypes={shellTypes} /> <GodzillaTabContent form={form} shellTypes={shellTypes} />
<CommandTabContent form={form} shellTypes={shellTypes} /> <CommandTabContent form={form} shellTypes={shellTypes} />
<BehinderTabContent form={form} shellTypes={shellTypes} /> <BehinderTabContent form={form} shellTypes={shellTypes} />
@@ -2,19 +2,14 @@ import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form"; import { FormProvider, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card.tsx";
import { import {
FormControl, FormControl,
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel,
} from "@/components/ui/form.tsx"; } from "@/components/ui/form";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import type { PackerConfig } from "@/types/memshell"; import type { PackerConfig } from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema.ts"; import type { MemShellFormSchema } from "@/types/schema.ts";
@@ -1,11 +1,6 @@
import { ScrollTextIcon } from "lucide-react"; import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card.tsx";
export function QuickUsage() { export function QuickUsage() {
const { t } = useTranslation(["common", "memshell"]); const { t } = useTranslation(["common", "memshell"]);
@@ -15,7 +15,7 @@ export function AgentResult({
packResult: string; packResult: string;
generateResult?: MemShellResult; generateResult?: MemShellResult;
}>) { }>) {
const { t } = useTranslation(); const { t } = useTranslation(["common"]);
const isPureAgent = packMethod === "AgentJar"; const isPureAgent = packMethod === "AgentJar";
return ( return (
<Card> <Card>
@@ -3,13 +3,8 @@ import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
import { QuickUsage } from "@/components/memshell/quick-usage"; import { QuickUsage } from "@/components/memshell/quick-usage";
import { Button } from "@/components/ui/button.tsx"; import { Button } from "@/components/ui/button";
import { import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs.tsx";
import { downloadBytes } from "@/lib/utils.ts"; import { downloadBytes } from "@/lib/utils.ts";
import type { MemShellResult } from "@/types/memshell"; import type { MemShellResult } from "@/types/memshell";
import CodeViewer from "../code-viewer"; import CodeViewer from "../code-viewer";
@@ -2,13 +2,9 @@ import { Shuffle } from "lucide-react";
import { Fragment, useEffect, useState } from "react"; import { Fragment, useEffect, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form"; import { FormProvider, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { FormField, FormFieldItem, FormFieldLabel } from "@/components/ui/form";
FormField, import { Input } from "@/components/ui/input";
FormFieldItem, import { Switch } from "@/components/ui/switch";
FormFieldLabel,
} from "@/components/ui/form.tsx";
import { Input } from "@/components/ui/input.tsx";
import { Switch } from "@/components/ui/switch.tsx";
import type { MemShellFormSchema } from "@/types/schema.ts"; import type { MemShellFormSchema } from "@/types/schema.ts";
export function OptionalClassFormField({ export function OptionalClassFormField({
@@ -37,7 +37,7 @@ export function CommandTabContent({
}>({ }>({
queryKey: ["commandConfigs"], queryKey: ["commandConfigs"],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`${env.API_URL}/config/command/configs`); const response = await fetch(`${env.API_URL}/api/config/command/configs`);
return await response.json(); return await response.json();
}, },
}); });
@@ -32,7 +32,7 @@ export default function CustomTabContent({
const { t } = useTranslation(["memshell", "common"]); const { t } = useTranslation(["memshell", "common"]);
const shellClassBase64 = form.watch("shellClassBase64"); const shellClassBase64 = form.watch("shellClassBase64");
const lastParsedBase64Ref = useRef<string | undefined>(undefined); const lastParsedBase64Ref = useRef<string | undefined>(undefined);
const classNameEndpoint = `${env.API_URL}/className`; const classNameEndpoint = `${env.API_URL}/api/className`;
useEffect(() => { useEffect(() => {
if (!shellClassBase64) { if (!shellClassBase64) {
@@ -5,14 +5,14 @@ import {
FormField, FormField,
FormFieldItem, FormFieldItem,
FormFieldLabel, FormFieldLabel,
} from "@/components/ui/form.tsx"; } from "@/components/ui/form";
import { import {
Select, Select,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select.tsx"; } from "@/components/ui/select";
import type { MemShellFormSchema } from "@/types/schema.ts"; import type { MemShellFormSchema } from "@/types/schema.ts";
export function ShellTypeFormField({ export function ShellTypeFormField({
@@ -5,8 +5,8 @@ import {
FormFieldItem, FormFieldItem,
FormFieldLabel, FormFieldLabel,
FormMessage, FormMessage,
} from "@/components/ui/form.tsx"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input.tsx"; import { Input } from "@/components/ui/input";
import { shouldHidden } from "@/lib/utils"; import { shouldHidden } from "@/lib/utils";
import type { MemShellFormSchema } from "@/types/schema.ts"; import type { MemShellFormSchema } from "@/types/schema.ts";
@@ -2,19 +2,14 @@ import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { FormProvider, type UseFormReturn } from "react-hook-form"; import { FormProvider, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card.tsx";
import { import {
FormControl, FormControl,
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel,
} from "@/components/ui/form.tsx"; } from "@/components/ui/form";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import type { PackerConfig } from "@/types/memshell"; import type { PackerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema"; import type { ProbeShellFormSchema } from "@/types/schema";
@@ -1,11 +1,6 @@
import { ScrollTextIcon } from "lucide-react"; import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card.tsx";
export function QuickUsage() { export function QuickUsage() {
const { t } = useTranslation(["common", "probeshell"]); const { t } = useTranslation(["common", "probeshell"]);
@@ -1,11 +1,6 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { QuickUsage } from "@/components/probeshell/quick-usage"; import { QuickUsage } from "@/components/probeshell/quick-usage";
import { import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "@/components/ui/tabs.tsx";
import type { ProbeShellResult } from "@/types/probeshell"; import type { ProbeShellResult } from "@/types/probeshell";
import CodeViewer from "../code-viewer"; import CodeViewer from "../code-viewer";
import { MultiPackResult } from "../memshell/results/multi-packer"; import { MultiPackResult } from "../memshell/results/multi-packer";
+49
View File
@@ -0,0 +1,49 @@
import { create } from "@orama/orama";
import { useDocsSearch } from "fumadocs-core/search/client";
import {
SearchDialog,
SearchDialogClose,
SearchDialogContent,
SearchDialogHeader,
SearchDialogIcon,
SearchDialogInput,
SearchDialogList,
SearchDialogOverlay,
type SharedProps,
} from "fumadocs-ui/components/dialog/search";
import { useI18n } from "fumadocs-ui/contexts/i18n";
function initOrama() {
return create({
schema: { _: "string" },
language: "english",
});
}
export default function DefaultSearchDialog(props: SharedProps) {
const { locale } = useI18n();
const { search, setSearch, query } = useDocsSearch({
type: "static",
initOrama,
locale,
});
return (
<SearchDialog
search={search}
onSearchChange={setSearch}
isLoading={query.isLoading}
{...props}
>
<SearchDialogOverlay />
<SearchDialogContent>
<SearchDialogHeader>
<SearchDialogIcon />
<SearchDialogInput />
<SearchDialogClose />
</SearchDialogHeader>
<SearchDialogList items={query.data !== "empty" ? query.data : null} />
</SearchDialogContent>
</SearchDialog>
);
}
@@ -1,21 +1,34 @@
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"; import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils"; 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} />;
} }
function AlertDialogTrigger({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) { function AlertDialogTrigger({
return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />; ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
);
} }
function AlertDialogPortal({ ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) { function AlertDialogPortal({
return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />; ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
);
} }
function AlertDialogOverlay({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) { function AlertDialogOverlay({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
return ( return (
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay" data-slot="alert-dialog-overlay"
@@ -28,7 +41,10 @@ function AlertDialogOverlay({ className, ...props }: React.ComponentProps<typeof
); );
} }
function AlertDialogContent({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) { function AlertDialogContent({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
return ( return (
<AlertDialogPortal> <AlertDialogPortal>
<AlertDialogOverlay /> <AlertDialogOverlay />
@@ -44,7 +60,10 @@ function AlertDialogContent({ className, ...props }: React.ComponentProps<typeof
); );
} }
function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) { function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="alert-dialog-header" data-slot="alert-dialog-header"
@@ -54,17 +73,26 @@ function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">)
); );
} }
function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) { function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="alert-dialog-footer" data-slot="alert-dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className,
)}
{...props} {...props}
/> />
); );
} }
function AlertDialogTitle({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) { function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return ( return (
<AlertDialogPrimitive.Title <AlertDialogPrimitive.Title
data-slot="alert-dialog-title" data-slot="alert-dialog-title"
@@ -87,12 +115,28 @@ function AlertDialogDescription({
); );
} }
function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) { function AlertDialogAction({
return <AlertDialogPrimitive.Action className={cn(buttonVariants(), className)} {...props} />; className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
return (
<AlertDialogPrimitive.Action
className={cn(buttonVariants(), className)}
{...props}
/>
);
} }
function AlertDialogCancel({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) { function AlertDialogCancel({
return <AlertDialogPrimitive.Cancel className={cn(buttonVariants({ variant: "outline" }), className)} {...props} />; className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
return (
<AlertDialogPrimitive.Cancel
className={cn(buttonVariants({ variant: "outline" }), className)}
{...props}
/>
);
} }
export { export {
@@ -1,5 +1,5 @@
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -19,21 +19,38 @@ const alertVariants = cva(
}, },
); );
function Alert({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) { function Alert({
return <div data-slot="alert" role="alert" className={cn(alertVariants({ variant }), className)} {...props} />; className,
variant,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
);
} }
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="alert-title" data-slot="alert-title"
className={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)} className={cn(
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
className,
)}
{...props} {...props}
/> />
); );
} }
function AlertDescription({ className, ...props }: React.ComponentProps<"div">) { function AlertDescription({
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="alert-description" data-slot="alert-description"
@@ -1,7 +1,7 @@
import * as React from "react" import { Avatar as AvatarPrimitive } from "radix-ui";
import {Avatar as AvatarPrimitive} from "radix-ui" import type * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
function Avatar({ function Avatar({
className, className,
@@ -12,11 +12,11 @@ function Avatar({
data-slot="avatar" data-slot="avatar"
className={cn( className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full", "relative flex size-8 shrink-0 overflow-hidden rounded-full",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function AvatarImage({ function AvatarImage({
@@ -29,7 +29,7 @@ function AvatarImage({
className={cn("aspect-square size-full", className)} className={cn("aspect-square size-full", className)}
{...props} {...props}
/> />
) );
} }
function AvatarFallback({ function AvatarFallback({
@@ -41,11 +41,11 @@ function AvatarFallback({
data-slot="avatar-fallback" data-slot="avatar-fallback"
className={cn( className={cn(
"bg-muted flex size-full items-center justify-center rounded-full", "bg-muted flex size-full items-center justify-center rounded-full",
className className,
)} )}
{...props} {...props}
/> />
) );
} }
export { Avatar, AvatarImage, AvatarFallback } export { Avatar, AvatarImage, AvatarFallback };
@@ -1,6 +1,6 @@
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { Slot as SlotPrimitive } from "radix-ui"; import { Slot as SlotPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -9,11 +9,14 @@ const badgeVariants = cva(
{ {
variants: { variants: {
variant: { variant: {
default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", default:
secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", "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: 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", "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", outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -27,10 +30,17 @@ function Badge({
variant, variant,
asChild = false, asChild = false,
...props ...props
}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) { }: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? SlotPrimitive.Slot : "span"; const Comp = asChild ? SlotPrimitive.Slot : "span";
return <Comp data-slot="badge" className={cn(badgeVariants({ variant }), className)} {...props} />; return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
} }
export { Badge, badgeVariants }; export { Badge, badgeVariants };
@@ -1,6 +1,6 @@
import { ChevronRight, MoreHorizontal } from "lucide-react"; import { ChevronRight, MoreHorizontal } from "lucide-react";
import { Slot as SlotPrimitive } from "radix-ui"; import { Slot as SlotPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -22,7 +22,13 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
} }
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) { function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
return <li data-slot="breadcrumb-item" className={cn("inline-flex items-center gap-1.5", className)} {...props} />; return (
<li
data-slot="breadcrumb-item"
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
);
} }
function BreadcrumbLink({ function BreadcrumbLink({
@@ -35,7 +41,11 @@ function BreadcrumbLink({
const Comp = asChild ? SlotPrimitive.Slot : "a"; const Comp = asChild ? SlotPrimitive.Slot : "a";
return ( return (
<Comp data-slot="breadcrumb-link" className={cn("hover:text-foreground transition-colors", className)} {...props} /> <Comp
data-slot="breadcrumb-link"
className={cn("hover:text-foreground transition-colors", className)}
{...props}
/>
); );
} }
@@ -43,7 +53,6 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return ( return (
<span <span
data-slot="breadcrumb-page" data-slot="breadcrumb-page"
role="link"
aria-disabled="true" aria-disabled="true"
aria-current="page" aria-current="page"
className={cn("text-foreground font-normal", className)} className={cn("text-foreground font-normal", className)}
@@ -52,7 +61,11 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
); );
} }
function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">) { function BreadcrumbSeparator({
children,
className,
...props
}: React.ComponentProps<"li">) {
return ( return (
<li <li
data-slot="breadcrumb-separator" data-slot="breadcrumb-separator"
@@ -66,7 +79,10 @@ function BreadcrumbSeparator({ children, className, ...props }: React.ComponentP
); );
} }
function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">) { function BreadcrumbEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
return ( return (
<span <span
data-slot="breadcrumb-ellipsis" data-slot="breadcrumb-ellipsis"
@@ -1,6 +1,6 @@
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { Slot as SlotPrimitive } from "radix-ui"; import { Slot as SlotPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -9,13 +9,16 @@ const buttonVariants = cva(
{ {
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive: destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", secondary:
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
@@ -44,7 +47,13 @@ function Button({
}) { }) {
const Comp = asChild ? SlotPrimitive.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}
/>
);
} }
export { Button, buttonVariants }; export { Button, buttonVariants };
@@ -1,4 +1,4 @@
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -6,7 +6,10 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card" data-slot="card"
className={cn("bg-card text-card-foreground flex flex-col rounded-xl border pb-6 shadow-sm", className)} className={cn(
"border text-card-foreground flex flex-col rounded-xl pb-6 text-sm shadow-sm",
className,
)}
{...props} {...props}
/> />
); );
@@ -26,31 +29,64 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
} }
function CardTitle({ className, ...props }: React.ComponentProps<"div">) { function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="card-title" className={cn("leading-none font-semibold", className)} {...props} />; return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
);
} }
function CardDescription({ className, ...props }: React.ComponentProps<"div">) { function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="card-description" className={cn("text-muted-foreground text-sm", className)} {...props} />; return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
} }
function CardAction({ className, ...props }: React.ComponentProps<"div">) { function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-action" data-slot="card-action"
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)} className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className,
)}
{...props} {...props}
/> />
); );
} }
function CardContent({ className, ...props }: React.ComponentProps<"div">) { function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="card-content" className={cn("px-6", className)} {...props} />; return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
);
} }
function CardFooter({ className, ...props }: React.ComponentProps<"div">) { function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div data-slot="card-footer" className={cn("flex items-center px-6 [.border-t]:pt-6", className)} {...props} /> <div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
); );
} }
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }; export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};
@@ -1,10 +1,13 @@
import { CheckIcon } from "lucide-react"; import { CheckIcon } from "lucide-react";
import { Checkbox as CheckboxPrimitive } from "radix-ui"; import { Checkbox as CheckboxPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) { function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return ( return (
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
data-slot="checkbox" data-slot="checkbox"
@@ -1,31 +1,54 @@
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { ContextMenu as ContextMenuPrimitive } from "radix-ui"; import { ContextMenu as ContextMenuPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function ContextMenu({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Root>) { function ContextMenu({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />; return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />;
} }
function ContextMenuTrigger({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) { function ContextMenuTrigger({
return <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />; ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
return (
<ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
);
} }
function ContextMenuGroup({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Group>) { function ContextMenuGroup({
return <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />; ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
return (
<ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
);
} }
function ContextMenuPortal({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) { function ContextMenuPortal({
return <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />; ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
return (
<ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
);
} }
function ContextMenuSub({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) { function ContextMenuSub({
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />; return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />;
} }
function ContextMenuRadioGroup({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) { function ContextMenuRadioGroup({
return <ContextMenuPrimitive.RadioGroup data-slot="context-menu-radio-group" {...props} />; ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
return (
<ContextMenuPrimitive.RadioGroup
data-slot="context-menu-radio-group"
{...props}
/>
);
} }
function ContextMenuSubTrigger({ function ContextMenuSubTrigger({
@@ -52,7 +75,10 @@ function ContextMenuSubTrigger({
); );
} }
function ContextMenuSubContent({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) { function ContextMenuSubContent({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
return ( return (
<ContextMenuPrimitive.SubContent <ContextMenuPrimitive.SubContent
data-slot="context-menu-sub-content" data-slot="context-menu-sub-content"
@@ -65,7 +91,10 @@ function ContextMenuSubContent({ className, ...props }: React.ComponentProps<typ
); );
} }
function ContextMenuContent({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Content>) { function ContextMenuContent({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
return ( return (
<ContextMenuPrimitive.Portal> <ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content <ContextMenuPrimitive.Content
@@ -164,13 +193,19 @@ function ContextMenuLabel({
<ContextMenuPrimitive.Label <ContextMenuPrimitive.Label
data-slot="context-menu-label" data-slot="context-menu-label"
data-inset={inset} data-inset={inset}
className={cn("text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)} className={cn(
"text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className,
)}
{...props} {...props}
/> />
); );
} }
function ContextMenuSeparator({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) { function ContextMenuSeparator({
className,
...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
return ( return (
<ContextMenuPrimitive.Separator <ContextMenuPrimitive.Separator
data-slot="context-menu-separator" data-slot="context-menu-separator"
@@ -180,11 +215,17 @@ function ContextMenuSeparator({ className, ...props }: React.ComponentProps<type
); );
} }
function ContextMenuShortcut({ className, ...props }: React.ComponentProps<"span">) { function ContextMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return ( return (
<span <span
data-slot="context-menu-shortcut" data-slot="context-menu-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)} className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className,
)}
{...props} {...props}
/> />
); );
@@ -1,26 +1,37 @@
import { XIcon } from "lucide-react"; import { XIcon } from "lucide-react";
import { Dialog as DialogPrimitive } from "radix-ui"; import { Dialog as DialogPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) { function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />; return <DialogPrimitive.Root data-slot="dialog" {...props} />;
} }
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) { function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />; return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
} }
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) { function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />; return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
} }
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) { function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />; return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
} }
function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>) { function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return ( return (
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
data-slot="dialog-overlay" data-slot="dialog-overlay"
@@ -47,7 +58,7 @@ function DialogContent({
<DialogPrimitive.Content <DialogPrimitive.Content
data-slot="dialog-content" data-slot="dialog-content"
className={cn( 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", "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, className,
)} )}
{...props} {...props}
@@ -81,13 +92,19 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="dialog-footer" data-slot="dialog-footer"
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className,
)}
{...props} {...props}
/> />
); );
} }
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) { function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return ( return (
<DialogPrimitive.Title <DialogPrimitive.Title
data-slot="dialog-title" data-slot="dialog-title"
@@ -97,7 +114,10 @@ function DialogTitle({ className, ...props }: React.ComponentProps<typeof Dialog
); );
} }
function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>) { function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return ( return (
<DialogPrimitive.Description <DialogPrimitive.Description
data-slot="dialog-description" data-slot="dialog-description"
@@ -1,19 +1,32 @@
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"; import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { function DropdownMenu({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />; return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
} }
function DropdownMenuPortal({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) { function DropdownMenuPortal({
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />; ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
);
} }
function DropdownMenuTrigger({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) { function DropdownMenuTrigger({
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />; ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
{...props}
/>
);
} }
function DropdownMenuContent({ function DropdownMenuContent({
@@ -36,8 +49,12 @@ function DropdownMenuContent({
); );
} }
function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { function DropdownMenuGroup({
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />; ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
);
} }
function DropdownMenuItem({ function DropdownMenuItem({
@@ -89,8 +106,15 @@ function DropdownMenuCheckboxItem({
); );
} }
function DropdownMenuRadioGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) { function DropdownMenuRadioGroup({
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />; ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return (
<DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group"
{...props}
/>
);
} }
function DropdownMenuRadioItem({ function DropdownMenuRadioItem({
@@ -128,13 +152,19 @@ function DropdownMenuLabel({
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label" data-slot="dropdown-menu-label"
data-inset={inset} data-inset={inset}
className={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)} className={cn(
"px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className,
)}
{...props} {...props}
/> />
); );
} }
function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) { function DropdownMenuSeparator({
className,
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return ( return (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator" data-slot="dropdown-menu-separator"
@@ -144,17 +174,25 @@ function DropdownMenuSeparator({ className, ...props }: React.ComponentProps<typ
); );
} }
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) { function DropdownMenuShortcut({
className,
...props
}: React.ComponentProps<"span">) {
return ( return (
<span <span
data-slot="dropdown-menu-shortcut" data-slot="dropdown-menu-shortcut"
className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)} className={cn(
"text-muted-foreground ml-auto text-xs tracking-widest",
className,
)}
{...props} {...props}
/> />
); );
} }
function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) { function DropdownMenuSub({
...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />; return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
} }
@@ -1,4 +1,4 @@
import { Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui"; import { type Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui";
import * as React from "react"; import * as React from "react";
import { import {
Controller, Controller,
@@ -21,7 +21,9 @@ type FormFieldContextValue<
name: TName; name: TName;
}; };
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue); const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
);
const FormField = < const FormField = <
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
@@ -63,14 +65,20 @@ type FormItemContextValue = {
id: string; id: string;
}; };
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue); const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
);
function FormItem({ className, ...props }: React.ComponentProps<"div">) { function FormItem({ className, ...props }: React.ComponentProps<"div">) {
const id = React.useId(); const id = React.useId();
return ( return (
<FormItemContext.Provider value={{ id }}> <FormItemContext.Provider value={{ id }}>
<div data-slot="form-item" className={cn("gap-2", className)} {...props} /> <div
data-slot="form-item"
className={cn("gap-2", className)}
{...props}
/>
</FormItemContext.Provider> </FormItemContext.Provider>
); );
} }
@@ -80,12 +88,19 @@ function FormFieldItem({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<FormItemContext.Provider value={{ id }}> <FormItemContext.Provider value={{ id }}>
<div data-slot="form-item" className={cn("flex flex-col gap-1", className)} {...props} /> <div
data-slot="form-item"
className={cn("flex flex-col gap-1", className)}
{...props}
/>
</FormItemContext.Provider> </FormItemContext.Provider>
); );
} }
function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) { function FormLabel({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const { error, formItemId } = useFormField(); const { error, formItemId } = useFormField();
return ( return (
@@ -99,7 +114,10 @@ function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPri
); );
} }
function FormFieldLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) { function FormFieldLabel({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const { error, formItemId } = useFormField(); const { error, formItemId } = useFormField();
return ( return (
@@ -113,14 +131,21 @@ function FormFieldLabel({ className, ...props }: React.ComponentProps<typeof Lab
); );
} }
function FormControl({ ...props }: React.ComponentProps<typeof SlotPrimitive.Slot>) { function FormControl({
const { error, formItemId, formDescriptionId, formMessageId } = useFormField(); ...props
}: React.ComponentProps<typeof SlotPrimitive.Slot>) {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return ( return (
<SlotPrimitive.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}`
}
aria-invalid={!!error} aria-invalid={!!error}
{...props} {...props}
/> />
@@ -149,7 +174,12 @@ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
} }
return ( return (
<p data-slot="form-message" id={formMessageId} className={cn("text-destructive text-sm", className)} {...props}> <p
data-slot="form-message"
id={formMessageId}
className={cn("text-destructive text-sm", className)}
{...props}
>
{body} {body}
</p> </p>
); );
@@ -1,4 +1,4 @@
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -1,9 +1,12 @@
import { Label as LabelPrimitive } from "radix-ui"; import { Label as LabelPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) { function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return ( return (
<LabelPrimitive.Root <LabelPrimitive.Root
data-slot="label" data-slot="label"
@@ -1,7 +1,7 @@
import { cva } from "class-variance-authority"; import { cva } from "class-variance-authority";
import { ChevronDownIcon } from "lucide-react"; import { ChevronDownIcon } from "lucide-react";
import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui"; import { NavigationMenu as NavigationMenuPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -17,7 +17,10 @@ function NavigationMenu({
<NavigationMenuPrimitive.Root <NavigationMenuPrimitive.Root
data-slot="navigation-menu" data-slot="navigation-menu"
data-viewport={viewport} data-viewport={viewport}
className={cn("group/navigation-menu relative flex max-w-max flex-1 items-center justify-center", className)} className={cn(
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
className,
)}
{...props} {...props}
> >
{children} {children}
@@ -26,24 +29,37 @@ function NavigationMenu({
); );
} }
function NavigationMenuList({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.List>) { function NavigationMenuList({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return ( return (
<NavigationMenuPrimitive.List <NavigationMenuPrimitive.List
data-slot="navigation-menu-list" data-slot="navigation-menu-list"
className={cn("group flex flex-1 list-none items-center justify-center gap-1", className)} className={cn(
"group flex flex-1 list-none items-center justify-center gap-1",
className,
)}
{...props} {...props}
/> />
); );
} }
function NavigationMenuItem({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) { function NavigationMenuItem({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
return ( return (
<NavigationMenuPrimitive.Item data-slot="navigation-menu-item" className={cn("relative", className)} {...props} /> <NavigationMenuPrimitive.Item
data-slot="navigation-menu-item"
className={cn("relative", className)}
{...props}
/>
); );
} }
const navigationMenuTriggerStyle = cva( 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", "group inline-flex h-9 w-max items-center justify-center rounded-md 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({ function NavigationMenuTrigger({
@@ -66,7 +82,10 @@ function NavigationMenuTrigger({
); );
} }
function NavigationMenuContent({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) { function NavigationMenuContent({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return ( return (
<NavigationMenuPrimitive.Content <NavigationMenuPrimitive.Content
data-slot="navigation-menu-content" data-slot="navigation-menu-content"
@@ -85,7 +104,11 @@ function NavigationMenuViewport({
...props ...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) { }: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return ( return (
<div className={cn("absolute top-full left-0 isolate z-50 flex justify-center")}> <div
className={cn(
"absolute top-full left-0 isolate z-50 flex justify-center",
)}
>
<NavigationMenuPrimitive.Viewport <NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport" data-slot="navigation-menu-viewport"
className={cn( className={cn(
@@ -98,7 +121,10 @@ function NavigationMenuViewport({
); );
} }
function NavigationMenuLink({ className, ...props }: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) { function NavigationMenuLink({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
return ( return (
<NavigationMenuPrimitive.Link <NavigationMenuPrimitive.Link
data-slot="navigation-menu-link" data-slot="navigation-menu-link"
@@ -1,13 +1,17 @@
import { Popover as PopoverPrimitive } from "radix-ui"; import { Popover as PopoverPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) { function Popover({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />; return <PopoverPrimitive.Root data-slot="popover" {...props} />;
} }
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) { function PopoverTrigger({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />; return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
} }
@@ -33,7 +37,9 @@ function PopoverContent({
); );
} }
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) { function PopoverAnchor({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />; return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
} }
@@ -1,14 +1,26 @@
import { CircleIcon } from "lucide-react"; import { CircleIcon } from "lucide-react";
import { RadioGroup as RadioGroupPrimitive } from "radix-ui"; import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) { function RadioGroup({
return <RadioGroupPrimitive.Root data-slot="radio-group" className={cn("grid gap-2", className)} {...props} />; className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn("grid gap-2", className)}
{...props}
/>
);
} }
function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) { function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return ( return (
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
data-slot="radio-group-item" data-slot="radio-group-item"
@@ -1,11 +1,19 @@
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"; import { ScrollArea as ScrollAreaPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) { function ScrollArea({
className,
children,
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return ( return (
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...props}> <ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("relative", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport <ScrollAreaPrimitive.Viewport
data-slot="scroll-area-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" className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
@@ -29,8 +37,10 @@ function ScrollBar({
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"flex touch-none p-px transition-colors select-none", "flex touch-none p-px transition-colors select-none",
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent", orientation === "vertical" &&
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent", "h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className, className,
)} )}
{...props} {...props}
@@ -1,18 +1,24 @@
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { Select as SelectPrimitive } from "radix-ui"; import { Select as SelectPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) { function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />; return <SelectPrimitive.Root data-slot="select" {...props} />;
} }
function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) { function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />; return <SelectPrimitive.Group data-slot="select-group" {...props} />;
} }
function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) { function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />; return <SelectPrimitive.Value data-slot="select-value" {...props} />;
} }
@@ -77,7 +83,10 @@ function SelectContent({
); );
} }
function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) { function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return ( return (
<SelectPrimitive.Label <SelectPrimitive.Label
data-slot="select-label" data-slot="select-label"
@@ -87,7 +96,11 @@ function SelectLabel({ className, ...props }: React.ComponentProps<typeof Select
); );
} }
function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) { function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return ( return (
<SelectPrimitive.Item <SelectPrimitive.Item
data-slot="select-item" data-slot="select-item"
@@ -107,7 +120,10 @@ function SelectItem({ className, children, ...props }: React.ComponentProps<type
); );
} }
function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) { function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return ( return (
<SelectPrimitive.Separator <SelectPrimitive.Separator
data-slot="select-separator" data-slot="select-separator"
@@ -117,11 +133,17 @@ function SelectSeparator({ className, ...props }: React.ComponentProps<typeof Se
); );
} }
function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) { function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return ( return (
<SelectPrimitive.ScrollUpButton <SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button" data-slot="select-scroll-up-button"
className={cn("flex cursor-default items-center justify-center py-1", className)} className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props} {...props}
> >
<ChevronUpIcon className="size-4" /> <ChevronUpIcon className="size-4" />
@@ -136,7 +158,10 @@ function SelectScrollDownButton({
return ( return (
<SelectPrimitive.ScrollDownButton <SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button" data-slot="select-scroll-down-button"
className={cn("flex cursor-default items-center justify-center py-1", className)} className={cn(
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props} {...props}
> >
<ChevronDownIcon className="size-4" /> <ChevronDownIcon className="size-4" />
@@ -1,5 +1,5 @@
import { Separator as SeparatorPrimitive } from "radix-ui"; import { Separator as SeparatorPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -1,5 +1,5 @@
import { Toaster as Sonner, ToasterProps } from "sonner"; import { useTheme } from "next-themes";
import { useTheme } from "../theme-provider"; import { Toaster as Sonner, type ToasterProps } from "sonner";
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme } = useTheme(); const { theme } = useTheme();
@@ -1,9 +1,12 @@
import { Switch as SwitchPrimitive } from "radix-ui"; import { Switch as SwitchPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) { function Switch({
className,
...props
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
return ( return (
<SwitchPrimitive.Root <SwitchPrimitive.Root
data-slot="switch" data-slot="switch"
@@ -1,28 +1,50 @@
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Table({ className, ...props }: React.ComponentProps<"table">) { function Table({ className, ...props }: React.ComponentProps<"table">) {
return ( return (
<div data-slot="table-container" className="relative w-full overflow-x-auto"> <div
<table data-slot="table" className={cn("w-full caption-bottom text-sm", className)} {...props} /> 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> </div>
); );
} }
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />; return (
<thead
data-slot="table-header"
className={cn("[&_tr]:border-b", className)}
{...props}
/>
);
} }
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
return <tbody data-slot="table-body" className={cn("[&_tr:last-child]:border-0", className)} {...props} />; return (
<tbody
data-slot="table-body"
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
);
} }
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
return ( return (
<tfoot <tfoot
data-slot="table-footer" data-slot="table-footer"
className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)} className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
className,
)}
{...props} {...props}
/> />
); );
@@ -32,7 +54,10 @@ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
return ( return (
<tr <tr
data-slot="table-row" data-slot="table-row"
className={cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className)} className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
className,
)}
{...props} {...props}
/> />
); );
@@ -64,10 +89,26 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
); );
} }
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) { function TableCaption({
className,
...props
}: React.ComponentProps<"caption">) {
return ( return (
<caption data-slot="table-caption" className={cn("text-muted-foreground mt-4 text-sm", className)} {...props} /> <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 }; export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
};
+64
View File
@@ -0,0 +1,64 @@
import { Tabs as TabsPrimitive } from "radix-ui";
import type * as React from "react";
import { cn } from "@/lib/utils";
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
);
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className,
)}
{...props}
/>
);
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
);
}
export { Tabs, TabsList, TabsTrigger, TabsContent };
@@ -1,4 +1,4 @@
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -1,13 +1,24 @@
import { Tooltip as TooltipPrimitive } from "radix-ui"; import { Tooltip as TooltipPrimitive } from "radix-ui";
import * as React from "react"; import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) { function TooltipProvider({
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />; delayDuration = 0,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delayDuration={delayDuration}
{...props}
/>
);
} }
function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) { function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return ( return (
<TooltipProvider> <TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} /> <TooltipPrimitive.Root data-slot="tooltip" {...props} />
@@ -15,7 +26,9 @@ function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root
); );
} }
function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) { function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />; return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
} }
-1
View File
@@ -47,7 +47,6 @@ function safeParseYup<T>(schema: yup.ObjectSchema<any>, data: unknown) {
} }
const createEnv = () => { const createEnv = () => {
// @ts-expect-error
const envVars = Object.entries(import.meta.env).reduce< const envVars = Object.entries(import.meta.env).reduce<
Record<string, string> Record<string, string>
>((acc, curr) => { >((acc, curr) => {
+50
View File
@@ -0,0 +1,50 @@
import browserCollections from "fumadocs-mdx:collections/browser";
import { useFumadocsLoader } from "fumadocs-core/source/client";
import { DocsLayout } from "fumadocs-ui/layouts/docs";
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
} from "fumadocs-ui/layouts/docs/page";
import defaultMdxComponents from "fumadocs-ui/mdx";
import { baseOptions } from "@/lib/layout.shared";
import { source } from "@/lib/source";
import type { Route } from "./+types/page";
export async function loader({ params }: Route.LoaderArgs) {
const slugs = params["*"].split("/").filter((v) => v.length > 0);
const page = source.getPage(slugs);
if (!page) throw new Response("Not found", { status: 404 });
return {
path: page.path,
pageTree: await source.serializePageTree(source.pageTree),
};
}
const clientLoader = browserCollections.docs.createClientLoader({
component({ toc, default: Mdx, frontmatter }) {
return (
<DocsPage toc={toc}>
<title>{frontmatter.title}</title>
<meta name="description" content={frontmatter.description} />
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<DocsBody>
<Mdx components={{ ...defaultMdxComponents }} />
</DocsBody>
</DocsPage>
);
},
});
export default function Page({ loaderData }: Route.ComponentProps) {
const Content = clientLoader.getComponent(loaderData.path);
const { pageTree } = useFumadocsLoader(loaderData);
return (
<DocsLayout {...baseOptions()} tree={pageTree}>
<Content />
</DocsLayout>
);
}
+17
View File
@@ -0,0 +1,17 @@
import { stopwords as mandarinStopwords } from "@orama/stopwords/mandarin";
import { createTokenizer } from "@orama/tokenizers/mandarin";
import { createFromSource } from "fumadocs-core/search/server";
import { source } from "@/lib/source";
const server = createFromSource(source, {
components: {
tokenizer: createTokenizer({
language: "mandarin",
stopWords: mandarinStopwords,
}),
},
});
export async function loader() {
return server.staticGET();
}
@@ -34,5 +34,6 @@
"urlPattern": "URL Pattern", "urlPattern": "URL Pattern",
"usage": "Usage", "usage": "Usage",
"version.updateAvailable": "Update Available", "version.updateAvailable": "Update Available",
"version.updateAvailableTooltip": "Click to Open Github Release Page ( v{{currentVersion}} -> v{{latestVersion}})" "version.updateAvailableTooltip": "Click to Open Github Release Page ( v{{currentVersion}} -> v{{latestVersion}})",
"shellTool": "Shell Tool"
} }
@@ -34,5 +34,6 @@
"urlPattern": "请求路径", "urlPattern": "请求路径",
"usage": "使用指南", "usage": "使用指南",
"version.updateAvailable": "有可用升级", "version.updateAvailable": "有可用升级",
"version.updateAvailableTooltip": "点击前往 GitHub Release ( v{{currentVersion}} -> v{{latestVersion}})" "version.updateAvailableTooltip": "点击前往 GitHub Release ( v{{currentVersion}} -> v{{latestVersion}})",
"shellTool": "内存马工具"
} }
@@ -8,15 +8,18 @@ import probeshellEN from "@/i18n/probeshell/en.json";
import probeshellZH from "@/i18n/probeshell/zh-CN.json"; import probeshellZH from "@/i18n/probeshell/zh-CN.json";
const getStoredLanguage = () => { const getStoredLanguage = () => {
if (typeof window === "undefined") {
return "zh-CN";
}
const storedLang = localStorage.getItem("i18nextLng"); const storedLang = localStorage.getItem("i18nextLng");
if (storedLang && ["en", "zh-CN"].includes(storedLang)) { if (storedLang && ["en", "zh-CN"].includes(storedLang)) {
return storedLang; return storedLang;
} }
const browserLang = navigator.language.split("-")[0]; const browserLang = navigator.language.split("-")[0];
return ["en", "zh-CN"].includes(browserLang) ? browserLang : "en"; return ["en", "zh-CN"].includes(browserLang) ? browserLang : "zh-CN";
}; };
const fallbackLng = "en"; const fallbackLng = "zh-CN";
export const ns = [ export const ns = [
"default", "default",
"common", "common",
@@ -51,7 +54,9 @@ i18n.use(initReactI18next).init({
}); });
i18n.on("languageChanged", (lng) => { i18n.on("languageChanged", (lng) => {
if (typeof window !== "undefined") {
localStorage.setItem("i18nextLng", lng); localStorage.setItem("i18nextLng", lng);
}
}); });
export default i18n; export default i18n;
+36
View File
@@ -0,0 +1,36 @@
import type { LinkItemType } from "fumadocs-ui/layouts/shared";
import { LanguageSwitcher } from "@/components/language-switcher";
export const siteConfig = {
name: "MemShellParty",
url: "https://party.memshell.news",
github: "https://github.com/ReaJason/MemShellParty",
latestRelease: "https://github.com/ReaJason/MemShellParty/releases/latest",
author: "ReaJason",
authorGithub: "https://github.com/ReaJason",
authorIntro: "Java RASP Developer",
blog: "https://reajason.eu.org",
navLinks: [
{
text: "MemShellGenerator",
url: "/memshell",
},
{
text: "ProbeShellGenerator",
url: "/probeshell",
},
{
text: "Documents",
url: "/docs",
},
{
text: "About",
url: "/about",
},
{
type: "custom",
children: <LanguageSwitcher />,
secondary: true,
},
] as LinkItemType[],
};
+9
View File
@@ -0,0 +1,9 @@
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
export function baseOptions(): BaseLayoutProps {
return {
githubUrl: "https://github.com/ReaJason/MemShellParty",
nav: {
title: "MemShellParty",
},
};
}
+9
View File
@@ -0,0 +1,9 @@
import { loader } from "fumadocs-core/source";
import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons";
import { docs } from "../../.source/server";
export const source = loader({
source: docs.toFumadocsSource(),
baseUrl: "/docs",
plugins: [lucideIconsPlugin()],
});
+88
View File
@@ -0,0 +1,88 @@
import { RootProvider } from "fumadocs-ui/provider/react-router";
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "react-router";
import type { Route } from "./+types/root";
import "./app.css";
import { I18nextProvider } from "react-i18next";
import SearchDialog from "@/components/search";
import { TailwindIndicator } from "@/components/tailwind-indicator";
import { Toaster } from "@/components/ui/sonner";
import { env } from "@/config";
import i18n from "./i18n/i18n";
import { QueryProvider } from "./providers/query-client-provider";
export const links: Route.LinksFunction = () => [
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossOrigin: "anonymous",
},
{
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
},
];
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="flex flex-col min-h-screen">
<RootProvider search={{ SearchDialog }}>
<Toaster />
<QueryProvider>
<I18nextProvider i18n={i18n}>{children}</I18nextProvider>
</QueryProvider>
{env.MODE !== "production" && <TailwindIndicator />}
</RootProvider>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = "Oops!";
let details = "An unexpected error occurred.";
let stack: string | undefined;
if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error";
details =
error.status === 404
? "The requested page could not be found."
: error.statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}
return (
<main className="pt-16 p-4 container mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
);
}
+12
View File
@@ -0,0 +1,12 @@
import { index, type RouteConfig, route } from "@react-router/dev/routes";
export default [
index("routes/memshell.tsx", {
id: "index-memshell",
}),
route("docs/*", "docs/page.tsx"),
route("api/search", "docs/search.ts"),
route("about", "routes/about.tsx"),
route("memshell", "routes/memshell.tsx"),
route("probeshell", "routes/probeshell.tsx"),
] satisfies RouteConfig;
+347
View File
@@ -0,0 +1,347 @@
import { useQuery } from "@tanstack/react-query";
import { motion } from "framer-motion";
import { HomeLayout } from "fumadocs-ui/layouts/home";
import {
AlertCircle,
Code,
Download,
ExternalLink,
Github,
Globe,
Heart,
Mail,
Package,
Shield,
User,
} from "lucide-react";
import { useTheme } from "next-themes";
import { Link } from "react-router";
import { LineShadowText } from "@/components/magicui/line-shadow-text";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { env } from "@/config";
import { siteConfig } from "@/lib/config";
import { baseOptions } from "../lib/layout.shared";
type VersionInfo = {
currentVersion: string;
latestVersion: string;
hasUpdate: boolean;
releaseUrl: string;
};
export default function AboutPage() {
const theme = useTheme();
const shadowColor = theme.theme === "dark" ? "#ffffff" : "#000000";
const {
data: updateInfo,
isPending,
error,
} = useQuery<VersionInfo>({
queryKey: ["version"],
queryFn: async () => {
const response = await fetch(`${env.API_URL}/api/version`);
if (response.ok) {
return await response.json();
}
return "unknown";
},
});
const inProduction = env.MODE === "production";
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.5,
},
},
};
return (
<HomeLayout {...baseOptions()} links={siteConfig.navLinks}>
<div className="min-h-screen font-sans 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>
</div>
</section>
{updateInfo?.hasUpdate && inProduction && (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
className="container mx-auto px-4 mb-8"
>
<Alert className="border-green-500 bg-green-50 dark:bg-green-900/20 w-auto">
<AlertCircle className="h-4 w-4 text-green-600 dark:text-green-400" />
<AlertDescription className="flex items-center justify-between flex-wrap gap-4">
<span className="text-green-800 dark:text-green-300">
New version {updateInfo.latestVersion} is available! (Current:{" "}
{updateInfo.currentVersion})
</span>
<a
href={siteConfig.latestRelease}
target="_blank"
rel="noopener noreferrer"
>
<Button
size="sm"
className="bg-green-600 hover:bg-green-700 text-white"
>
<Download className="mr-2 h-4 w-4" />
View Release
</Button>
</a>
</AlertDescription>
</Alert>
</motion.div>
)}
<motion.section
variants={containerVariants}
initial="hidden"
animate="visible"
className="container mx-auto px-4 py-16"
>
<div className="grid md:grid-cols-2 gap-8 max-w-6xl mx-auto">
<motion.div variants={itemVariants}>
<Card className="h-full">
<CardContent className="p-6">
<div className="flex items-center mb-4">
<Package className="w-6 h-6 mr-3 text-primary" />
<h2 className="text-2xl font-bold">Version</h2>
</div>
<div className="space-y-3">
<div className="flex justify-between items-center py-2 border-b border-border/50">
<span className="text-muted-foreground">
Current Version
</span>
<Badge variant="secondary">
{updateInfo?.currentVersion || "v0.0.0"}
</Badge>
</div>
<div className="flex justify-between items-center py-2 border-b border-border/50">
<span className="text-muted-foreground">
Latest Version
</span>
{isPending && (
<span className="text-sm text-gray-500">
Checking...
</span>
)}
{error && (
<Badge variant="destructive">{error.message}</Badge>
)}
{updateInfo && !error && (
<Badge variant="outline">
{updateInfo.latestVersion}
</Badge>
)}
</div>
<div className="flex justify-between items-center py-2 border-b border-border/50">
<span className="text-muted-foreground">License</span>
<Badge variant="outline">MIT License</Badge>
</div>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<span className="text-xs text-gray-500">
Last test time: {new Date().toLocaleString()}
</span>
</CardFooter>
</Card>
</motion.div>
<motion.div variants={itemVariants}>
<Card className="h-full">
<CardContent className="p-6">
<div className="flex items-center mb-4">
<User className="w-6 h-6 mr-3 text-primary" />
<h2 className="text-2xl font-bold">Author</h2>
</div>
<div className="space-y-4">
<div className="flex items-center gap-3">
<Avatar className="w-16 h-16">
<AvatarImage src="https://cdn.jsdelivr.net/gh/ReaJason/blog_imgs/default/blog_avatar.jpg" />
<AvatarFallback>RJ</AvatarFallback>
</Avatar>
<div>
<h3 className="font-semibold text-lg">
{siteConfig.author}
</h3>
<p className="text-sm text-muted-foreground">
{siteConfig.authorIntro}
</p>
</div>
</div>
<div className="space-y-2 pt-2">
<a
href={siteConfig.blog}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-muted-foreground hover:text-primary transition-colors"
>
<Globe className="w-4 h-4" />
<span className="text-sm">reajason.eu.org</span>
<ExternalLink className="w-3 h-3" />
</a>
<a
href={siteConfig.authorGithub}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-muted-foreground hover:text-primary transition-colors"
>
<Github className="w-4 h-4" />
<span className="text-sm">github.com/ReaJason</span>
<ExternalLink className="w-3 h-3" />
</a>
<div className="flex items-center gap-2 text-muted-foreground">
<Mail className="w-4 h-4" />
<span className="text-sm">Contact via GitHub</span>
</div>
</div>
</div>
</CardContent>
</Card>
</motion.div>
</div>
</motion.section>
<motion.section
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 0.6 }}
className="container mx-auto px-4 py-16"
>
<div className="max-w-6xl mx-auto">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold mb-4">Resources & Links</h2>
<p className="text-muted-foreground">
Explore documentation and contribute to the project
</p>
</div>
<div className="grid md:grid-cols-3 gap-6">
<Card className="group hover:shadow-lg transition-shadow">
<CardContent className="p-6">
<Code className="w-10 h-10 mb-4 text-primary group-hover:scale-110 transition-transform" />
<h3 className="font-semibold text-lg mb-2">Documentation</h3>
<p className="text-sm text-muted-foreground mb-4">
Comprehensive guides and API references for using
MemShellParty effectively.
</p>
<Link
className="text-primary hover:underline text-sm font-medium flex items-center gap-1"
to="/docs"
>
Read Docs <ExternalLink className="w-3 h-3" />
</Link>
</CardContent>
</Card>
<Card className="group hover:shadow-lg transition-shadow">
<CardContent className="p-6">
<Github className="w-10 h-10 mb-4 text-primary group-hover:scale-110 transition-transform" />
<h3 className="font-semibold text-lg mb-2">Source Code</h3>
<p className="text-sm text-muted-foreground mb-4">
View the source code, report issues, and contribute to the
development.
</p>
<a
href={siteConfig.github}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline text-sm font-medium flex items-center gap-1"
>
View Repository <ExternalLink className="w-3 h-3" />
</a>
</CardContent>
</Card>
<Card className="group hover:shadow-lg transition-shadow">
<CardContent className="p-6">
<Heart className="w-10 h-10 mb-4 text-primary group-hover:scale-110 transition-transform" />
<h3 className="font-semibold text-lg mb-2">Support</h3>
<p className="text-sm text-muted-foreground mb-4">
Star the project on GitHub and share it with the security
community.
</p>
<a
href={siteConfig.github}
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline text-sm font-medium flex items-center gap-1"
>
Star on GitHub <ExternalLink className="w-3 h-3" />
</a>
</CardContent>
</Card>
</div>
</div>
</motion.section>
<footer className="border-t py-8 mt-16">
<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">{siteConfig.name}</p>
<p className="text-xs text-muted-foreground">
Built with by{" "}
<a
href={siteConfig.blog}
rel="noreferrer noopener"
target="_blank"
className="font-medium hover:text-primary transition-colors"
>
{siteConfig.author}
</a>
</p>
</div>
<div className="text-xs text-muted-foreground">
© 2025 {siteConfig.name}. For authorized security testing only.
</div>
</div>
</footer>
</div>
</HomeLayout>
);
}
@@ -1,16 +1,17 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { HomeLayout } from "fumadocs-ui/layouts/home";
import { LoaderCircle, WandSparklesIcon } from "lucide-react"; import { LoaderCircle, WandSparklesIcon } from "lucide-react";
import { useState, useTransition } from "react"; import { useState, useTransition } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useLoaderData } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
import MainConfigCard from "@/components/memshell/main-config-card"; import MainConfigCard from "@/components/memshell/main-config-card";
import PackageConfigCard from "@/components/memshell/package-config-card"; import PackageConfigCard from "@/components/memshell/package-config-card";
import ShellResult from "@/components/memshell/shell-result"; import ShellResult from "@/components/memshell/shell-result";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Form } from "@/components/ui/form.tsx"; import { Form } from "@/components/ui/form";
import { env } from "@/config.ts"; import { env } from "@/config";
import { siteConfig } from "@/lib/config";
import { import {
type APIErrorResponse, type APIErrorResponse,
type MainConfig, type MainConfig,
@@ -24,16 +25,15 @@ import {
type MemShellFormSchema, type MemShellFormSchema,
memShellFormSchema, memShellFormSchema,
useYupValidationResolver, useYupValidationResolver,
} from "@/types/schema.ts"; } from "@/types/schema";
import { transformToPostData } from "@/utils/transformer.ts"; import { transformToPostData } from "@/utils/transformer";
import { baseOptions } from "../lib/layout.shared";
export default function MemShellPage() { export default function MemShellPage() {
const urlParams = useLoaderData();
const { data: serverConfig } = useQuery<ServerConfig>({ const { data: serverConfig } = useQuery<ServerConfig>({
queryKey: ["serverConfig"], queryKey: ["serverConfig"],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`${env.API_URL}/config/servers`); const response = await fetch(`${env.API_URL}/api/config/servers`);
return await response.json(); return await response.json();
}, },
}); });
@@ -41,7 +41,7 @@ export default function MemShellPage() {
const { data: mainConfig } = useQuery<MainConfig>({ const { data: mainConfig } = useQuery<MainConfig>({
queryKey: ["mainConfig"], queryKey: ["mainConfig"],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`${env.API_URL}/config`); const response = await fetch(`${env.API_URL}/api/config`);
return await response.json(); return await response.json();
}, },
}); });
@@ -49,7 +49,7 @@ export default function MemShellPage() {
const { data: packerConfig } = useQuery<PackerConfig>({ const { data: packerConfig } = useQuery<PackerConfig>({
queryKey: ["packerConfig"], queryKey: ["packerConfig"],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`${env.API_URL}/config/packers`); const response = await fetch(`${env.API_URL}/api/config/packers`);
return await response.json(); return await response.json();
}, },
}); });
@@ -58,27 +58,27 @@ export default function MemShellPage() {
const form = useForm({ const form = useForm({
resolver: useYupValidationResolver(memShellFormSchema, t), resolver: useYupValidationResolver(memShellFormSchema, t),
defaultValues: { defaultValues: {
server: urlParams.server ?? "Tomcat", server: "Tomcat",
serverVersion: urlParams.serverVersion ?? "unknown", serverVersion: "unknown",
targetJdkVersion: urlParams.targetJdkVersion ?? "50", targetJdkVersion: "50",
debug: urlParams.debug ?? false, debug: false,
byPassJavaModule: urlParams.byPassJavaModule ?? false, byPassJavaModule: false,
shellClassName: urlParams.shellClassName ?? "", shellClassName: "",
shellTool: urlParams.shellTool ?? ShellToolType.Godzilla, shellTool: ShellToolType.Godzilla,
shellType: urlParams.shellType ?? "Listener", shellType: "Listener",
urlPattern: urlParams.urlPattern ?? "/*", urlPattern: "/*",
godzillaPass: urlParams.godzillaPass ?? "", godzillaPass: "",
godzillaKey: urlParams.godzillaKey ?? "", godzillaKey: "",
commandParamName: urlParams.commandParamName ?? "", commandParamName: "",
behinderPass: urlParams.behinderPass ?? "", behinderPass: "",
antSwordPass: urlParams.antSwordPass ?? "", antSwordPass: "",
headerName: urlParams.headerName ?? "User-Agent", headerName: "User-Agent",
headerValue: urlParams.headerValue ?? "", headerValue: "",
injectorClassName: urlParams.injectorClassName ?? "", injectorClassName: "",
packingMethod: urlParams.packingMethod ?? "", packingMethod: "",
shrink: urlParams.shrink ?? true, shrink: true,
staticInitialize: true, staticInitialize: true,
shellClassBase64: urlParams.shellClassBase64 ?? "", shellClassBase64: "",
}, },
}); });
@@ -94,7 +94,7 @@ export default function MemShellPage() {
startTransition(async () => { startTransition(async () => {
try { try {
const postData = transformToPostData(data); const postData = transformToPostData(data);
const response = await fetch(`${env.API_URL}/memshell/generate`, { const response = await fetch(`${env.API_URL}/api/memshell/generate`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -123,7 +123,7 @@ export default function MemShellPage() {
}; };
return ( return (
<div className="bg-background"> <HomeLayout {...baseOptions()} links={siteConfig.navLinks}>
<div className="container mx-auto max-w-7xl p-4"> <div className="container mx-auto max-w-7xl p-4">
<Form {...form}> <Form {...form}>
<form <form
@@ -161,6 +161,6 @@ export default function MemShellPage() {
</form> </form>
</Form> </Form>
</div> </div>
</div> </HomeLayout>
); );
} }
@@ -1,4 +1,5 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { HomeLayout } from "fumadocs-ui/layouts/home";
import { LoaderCircle, WandSparklesIcon } from "lucide-react"; import { LoaderCircle, WandSparklesIcon } from "lucide-react";
import { useState, useTransition } from "react"; import { useState, useTransition } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
@@ -10,6 +11,7 @@ import ShellResult from "@/components/probeshell/shell-result";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Form } from "@/components/ui/form"; import { Form } from "@/components/ui/form";
import { env } from "@/config"; import { env } from "@/config";
import { siteConfig } from "@/lib/config";
import type { import type {
APIErrorResponse, APIErrorResponse,
PackerConfig, PackerConfig,
@@ -25,12 +27,13 @@ import {
useYupValidationProbeResolver, useYupValidationProbeResolver,
} from "@/types/schema"; } from "@/types/schema";
import { transformToProbePostData } from "@/utils/transformer"; import { transformToProbePostData } from "@/utils/transformer";
import { baseOptions } from "../lib/layout.shared";
export default function ProbeShellGenerator() { export default function ProbeShellGenerator() {
const { data: serverConfig } = useQuery<ServerConfig>({ const { data: serverConfig } = useQuery<ServerConfig>({
queryKey: ["serverConfig"], queryKey: ["serverConfig"],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`${env.API_URL}/config/servers`); const response = await fetch(`${env.API_URL}/api/config/servers`);
return await response.json(); return await response.json();
}, },
}); });
@@ -38,7 +41,7 @@ export default function ProbeShellGenerator() {
const { data: packerConfig } = useQuery<PackerConfig>({ const { data: packerConfig } = useQuery<PackerConfig>({
queryKey: ["packerConfig"], queryKey: ["packerConfig"],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`${env.API_URL}/config/packers`); const response = await fetch(`${env.API_URL}/api/config/packers`);
return await response.json(); return await response.json();
}, },
}); });
@@ -72,7 +75,7 @@ export default function ProbeShellGenerator() {
startTransition(async () => { startTransition(async () => {
try { try {
const postData = transformToProbePostData(data); const postData = transformToProbePostData(data);
const response = await fetch(`${env.API_URL}/probe/generate`, { const response = await fetch(`${env.API_URL}/api/probe/generate`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -100,7 +103,7 @@ export default function ProbeShellGenerator() {
}); });
}; };
return ( return (
<div className="bg-background"> <HomeLayout {...baseOptions()} links={siteConfig.navLinks}>
<div className="container mx-auto max-w-7xl p-4"> <div className="container mx-auto max-w-7xl p-4">
<Form {...form}> <Form {...form}>
<form <form
@@ -134,6 +137,6 @@ export default function ProbeShellGenerator() {
</form> </form>
</Form> </Form>
</div> </div>
</div> </HomeLayout>
); );
} }
@@ -101,9 +101,8 @@ export const useYupValidationResolver = (
if ( if (
values.server === "Jetty" && values.server === "Jetty" &&
(values.shellType === "Handler" (values.shellType === "Handler" ||
|| values.shellType === "JakartaHandler" values.shellType === "JakartaHandler") &&
) &&
values.serverVersion === "unknown" values.serverVersion === "unknown"
) { ) {
errors[serverVersion] = { errors[serverVersion] = {
@@ -81,7 +81,7 @@ export function generateShareableUrl(values: MemShellFormSchema): string {
const params = new URLSearchParams(); const params = new URLSearchParams();
// Helper function to add parameters only if they have non-default values // Helper function to add parameters only if they have non-default values
const addParam = (key: string, value: any, defaultValue: any) => { const addParam = (key: string, value: unknown, defaultValue: unknown) => {
if (value !== defaultValue) { if (value !== defaultValue) {
params.append(key, String(value)); params.append(key, String(value));
} }
+18 -19
View File
@@ -1,22 +1,13 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.1.4/schema.json", "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
"vcs": { "vcs": {
"enabled": false, "enabled": true,
"clientKind": "git", "clientKind": "git",
"useIgnoreFile": false "useIgnoreFile": true
}, },
"files": { "files": {
"ignoreUnknown": false, "ignoreUnknown": true,
"includes": [ "includes": ["**", "!node_modules", "!.source"]
"**",
"!**/node_modules",
"!**/.next",
"!**/dist",
"!**/.turbo",
"!**/.source",
"!**/convex/_generated",
"!**/components/ui"
]
}, },
"formatter": { "formatter": {
"enabled": true, "enabled": true,
@@ -28,15 +19,23 @@
"rules": { "rules": {
"recommended": true, "recommended": true,
"suspicious": { "suspicious": {
"noFallthroughSwitchClause": "off",
"noExplicitAny": "off" "noExplicitAny": "off"
} }
},
"domains": {
"react": "recommended"
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
} }
}, },
"assist": { "actions": { "source": { "organizeImports": "on" } } }, "css": {
"javascript": { "parser": {
"formatter": { "tailwindDirectives": true
"quoteStyle": "double"
} }
} }
} }
+1588
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
-20
View File
@@ -1,20 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "zinc",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
+357
View File
@@ -0,0 +1,357 @@
---
title: 更新日志
icon: ScrollText
---
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v2.2.0](https://github.com/ReaJason/MemShellParty/releases/tag/v2.2.0) - 2025-11-20
### Added
1. 内存马注入器支持接入回显 toString 打印 contextPath 等注入成功或错误信息(By @ReaJason
2. boot 新增通过字节码 base64 获取类名接口,并支持自定义内存马使用随机类名或原始类名
3. 适配 Apusic 9.0.1 版本(金蝶 EAS Cloud
4. UI 在 JSP/Base64/序列化相关 payload 生成时添加下载按钮便于下载 JSP 文件/注入器 Class 文件/原始序列化文件
5. 支持注入器或回显马添加静态代码块执行构造方法调用,解决部分场景下无法手动调用构造方法
6. 支持 SpringWebMVC 回显马生成(#107
7. 添加 Jetty 12 中 ee11 的内存马注入支持和靶场测试用例
8. 支持 ScriptEngineJar 打包方式(SnakeYaml 漏洞注入,#109
9. 支持 AbstractTranslet 打包方式,方便 TemplatesImpl 反序列化漏洞注入
10. 支持脚本引擎执行回显马生成,方便调试
### Fixed
1. 修复自定义内存马生成报错(#102、#106Thanks @love71 and @m0s30
2. 修复 Tomcat Valve 仅单个情况下注入 ProxyValve 导致站挂掉(#105 Thanks @love71
3. 默认哥斯拉内存马去除对 session 的依赖,解决部分场景下 session 为 null 导致无法连接
### Changed
1. 命令执行内存马和命令执行回显马支持从参数或请求头中获取命令参数
2. 调整靶场构建使用的 openjdk 改为 eclipse-temurin
3. 依赖更新
**Full Changelog:** [v2.1.0...v2.2.0](https://github.com/ReaJason/MemShellParty/compare/v2.1.0...v2.2.0)
## [v2.1.0](https://github.com/ReaJason/MemShellParty/releases/tag/v2.1.0) - 2025-08-12
### Added
1. 添加 BigInteger、ScriptEngineBigInteger 打包方式(#86 by @wanswu
2. 添加 SpELSpringGzipJDK17 打包方式(#83 by @xcxmiku and @ReaJason
3. 添加 JXPathSpringGzipPacker、JXPathSpringGzipPackerJDK17 打包方式(GeoServer 漏洞注入)
4. 添加 Base64URLEncoded 打包方式(配合回显马进行小马拉大马测试)
5. 支持回显马在进行自定义字节码执行时去除 Java 魔数流量特征
```http
/path/code?payload=yv66vgAAADIBVQEAJ29yZy9hcGFj...
```
改为只需要如下方式
```http
/path/code?payload=IBVQEAJ29yZy9hcGFj...
```
### Fixed
1. 修复非调试模式下,构造方法中的 e.printStackTrace() 并没有被移除
2. 修复使用 Dockerfile 进行自定义构建时,自定义路由无法正常工作
3. 修复探测内存马中 Sleep 和 DNSLog 自定义类名失效(#89 Thanks @yinsel
4. 修复自定义内存马中,不会自动调用 listener 添加 getResponseFromRequest 实现代码和 valve 修改包名的逻辑(使用自定义内存马请参考:[如何使用自定义内存马功能](/docs/WriteCustomShell.md) 进行实现,否则会出现不可用的问题)
5. 修复使用 SDK 时,Agent Packer 在 jar-with-dependencies(fatjar) 中会出现打包整个 jar 的问题
6. 修复 Tomcat Listener 注入会使之前所有 Listener 失效(#93
### Changed
1. 修改 Packer 中对于 Thread.currentThread().getContextClassLoader() 的纯依赖改为新建 URLClassLoader,使得回显马可多次执行
2. 去除 logbackjava11)和 okhttp 无用依赖,解决使用 SDK 打包部分场景会出现类版本不支持的问题
3. 实现 Lombok SuperBuilder 自定义 Builder 简化配置类的创建代码(#9f8f3baa)
4. 优化命令执行内存马,改为和回显马逻辑一致,使用 ProcessBuilder.redirectErrorStream 简化流读取
5. 修改 packer 中脚本存放添加 memshell-party 一级,防止打包成 fatjar 时文件全在根目录,可能会被覆盖导致功能破坏
6. 优化资源读取,通过工具类 loadTemplateFromResource 统一实现
7. 优化 Agent Attacher JDK11 异常处理
8. 依赖更新
**Full Changelog:** [v2.0.0...v2.1.0](https://github.com/ReaJason/MemShellParty/compare/v2.0.0...v2.1.0)
## [v2.0.0](https://github.com/ReaJason/MemShellParty/releases/tag/v2.0.0) - 2025-08-13
> [!WARNING]
> 为了区分内存马和探测马,部分类名和接口做了调整,如果使用了 SDK,需要参考:[examples/memshell-party-maven-example](https://github.com/ReaJason/MemShellParty/tree/master/examples/memshell-party-maven-example) 进行调整。
### Added
- **支持探测马生成** (#71 by @ReaJason,部分代码参考 jeg 与 java-chains)
- Web 添加关于页面
- 支持 H2 JDBC 打包方式(DataEase 漏洞注入)
- 支持 XMLDecoder 打包方式(WebLogic 漏洞注入)
- 支持 OGNL SpringUtils 打包方式(Confluence 漏洞注入)
### Fixed
- 修复 SpringWebMVC Agent 无法点击生成按钮 (#77)
- 修复 Spring Boot 对于 no static resource 老是抛出错误日志
- 修复 TongWeb8 context 获取错误导致注入失败的问题
### Changed
- **简化 Server 类型选择,例如 JBossEAP7 和 WildFly 选择 Undertow** (#74 by @zema1)
- **去除注入器中静态代码块调用构造方法,减少注入动作的触发**(可能会导致部分 `Class.forName("name", true, loader)` 的场景注入失败,后续会添加字节码 Web 工具进行这块的处理)
- 简化 Tomcat AgentInjector 的代码
- 前端 module 分包减少单个 js 体积,加快首次加载速度
- 移除 memshell-party-bom 模块,改用 gradle/libs.versions.toml,参考:[Use Version Catalogs to Centralize Dependency Versions](https://docs.gradle.org/current/userguide/best_practices_dependencies.html#use_version_catalogs)
- 使用 build-logic 替代 buildSrc,加快构建速度,参考:[Favor build-logic Composite Builds for Build Logic](https://docs.gradle.org/current/userguide/best_practices_general.html#favor_composite_builds)
- 从 generator 模块中分离 payload 生成代码并合并 deserialize 模块为 packer 模块
- 使用 i18 扁平化 key,并使用 namespace 区分 MemShell 和 ProbeShell 的字段,参考:[RSSNext/Folo/zh-CN.json](https://github.com/RSSNext/Folo/blob/dev/locales/common/zh-CN.json)
- 升级 gradle-maven-publish-plugin 插件版本,简化打包指令
- 统一生成内存马类过程中抛出异常为 GenerationException,并单独设置 GlobalExceptionHandler
**Full Changelog:** [v1.10.0...v2.0.0](https://github.com/ReaJason/MemShellParty/compare/v1.10.0...v2.0.0)
## [v1.10.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.10.0) - 2025-06-07
### Added
- 添加新的 JSP 打包方式(直接使用 defineClass 进行注入)(by @zema1 #67)
- 支持 Tomcat 和 JBossAS ProxyValve 内存马(通过动态代理将 StandardPipeline 的第一个 valve 进行包装注入自定义逻辑)
### Fixed
- 修复哥斯拉无法使用最新版连接
- 修复 TongWeb8 Valve 未适配
- 修复移动端 UI 输入框 placeholder 字体过大
- 修复移动端 UI 类名复制按钮超出卡片范围
### Changed
- 修改 Valve 和 Listener 字节码修改时机,改为生成时再进行修改,方便自定义内存马生成
- 合并 memshell 与 memshell-jdk8 模块,方便维护
- UI 使用新的 shadcn/ui 提供的 Zinc 主题配置
- 将所有 Shell 捕获异常从 Exception 改为 Throwable
- 简化 Shell base64 方法代码
- Gradle 更新至 8.14.2
- 参考 [General Gradle Best Practices](https://docs.gradle.org/current/userguide/best_practices_general.html),将构建脚本改为
Kotlin DSL
**Full Changelog:** [v1.9.0...v1.10.0](https://github.com/ReaJason/MemShellParty/compare/v1.9.0...v1.10.0)
## [v1.9.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.9.0) - 2025-05-28
### Added
- 支持 TongWeb8 内存马生成 by @ReaJason
- 通过 context 获取 webAppClassLoader,不再依赖 Thread.currentThread().getContextClassLoader()
为请求线程,参考:[任意类加载环境下注入内存马](https://reajason.eu.org/writing/whichclassloaderforshell/)
- 全面支持使用 ASM 生成 Agent(仅需 92.5 KB),并且可选 JDKAttacher 与 JREAttacher
- 支持命令执行自定义实现类,RuntimeExec or ForkAndExec
### Fixed
- 修复 Apusic Listener 由于 response 获取错误导致不可用
- 修复 Jakarta WebSocket 无法注入
### Changed
- Godzilla WebSocket 默认使用 AES_BASE64 加密器,支持使用 [GzWebsocket](https://github.com/xsshim/GzWebsocket) 插件进行连接。
- Gradle、Web 项目依赖更新
- UI 生成界面默认勾选缩小字节码
- UI 优化手机端选项布局,单行显示每个输入框
- UI 使用紧凑模式,隐藏非常用字段简化操作路径
- 提取公共 Tailwind CSS 类名,简化表单组件代码
- yup 替代 zod 减少打包体积,并将自定义表单验证融合到 react-hook-form 中优化 UX
- 重构 Shell Generator 代码
**Full Changelog:** [v1.8.0...v1.9.0](https://github.com/ReaJason/MemShellParty/compare/v1.8.0...v1.9.0)
## [v1.8.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.8.0) - 2025-05-14
### Added
- 支持普元中间件内存马生成(only 6.5 版本)by @ReaJason#60
- 支持哥斯拉 WebSocket 内存马生成与测试
- 添加 Groovy 通用恶意类加载打包方式(用于测试 Jenkins 脚本执行)
- 命令执行支持加密器,双 Base64 测试绕过 WAF 安全设备
### Fixed
- 修复 Jetty 高版本中 ee8 ~ ee10 无法注入(#61
- 修复 Spring Boot 下类加载的原因导致的 Tomcat/Jetty/Undertow 部分内存马注入失败
### Changed
- 命令执行改为反射调用 forkAndExec 以绕过 RASPJDK7+
- 获取所有线程代码改为 `Thread.getAllStackTraces().keySet()`,高版本 JDK 不再需要 bypass module
- 优化 boot 在启动时即触发 Server 的内存马生成注册,加速第一次请求访问
**Full Changelog:** [v1.7.0...v1.8.0](https://github.com/ReaJason/MemShellParty/compare/v1.7.0...v1.8.0)
## [v1.7.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.7.0) - 2025-04-06
### Added
- 支持发布到 MavenCentral,可通过引入依赖使用生成 API by @ReaJason#41
- 支持 CC3、CC4 反序列化 payload 打包方式
- 支持随机参数生成与默认选项(#50)
### Changed
- 去除代码混淆相关代码
- 为了更好地在 MavenCentral 展示,重命名部分模块
- 使用 Jackson 代替 Fastjson 降低 boot 打包体积
- 移除 commons-codec 降低 boot 打包体积
- 升级 shadcn/ui 所有 component 代码
**Full Changelog:** [v1.6.0...v1.7.0](https://github.com/ReaJason/MemShellParty/compare/v1.6.0...v1.7.0)
## [v1.6.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.6.0) - 2025-03-30
> 做代码生成以及代码混淆真是一件需要耐心的事情
### Added
- 支持自定义内存马生成 by @ReaJason#49
- 支持命令回显 ASM Agent 内存马 by @ReaJason#51
- 支持简易的代码混淆 by @ReaJason#13
- 支持自动发布 DEV 分支代码 CD
### Changed
- 简化 Jetty 获取 Context 代码
- 优化 Dockerfile 减小镜像体积
**Full Changelog:** [v1.5.0...v1.6.0](https://github.com/ReaJason/MemShellParty/compare/v1.5.0...v1.6.0)
## [v1.5.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.5.0) - 2025-03-01
### Added
- 支持 NeoreGeorg 内存马生成 by @ReaJason
- 支持 UI 显示更新按钮跳转到 GitHub Release 界面
### Changed
- 简化 Valve 内存马代码
- 升级 Gradle 8.13
**Full Changelog:** [v1.4.0...v1.5.0](https://github.com/ReaJason/MemShellParty/compare/v1.4.0...v1.5.0)
## [v1.4.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.4.0) - 2025-02-26
### Added
- 支持缩小字节码 (移除调试信息) by @ReaJason
- 支持 Tomcat Jakarta WebSocket
### Fixed
- 修复自定义注入器类名不起作用
### Changed
- 优化跨平台开发体验,将 bash 脚本改为 js 脚本
**Full Changelog:** [v1.3.2...v1.4.0](https://github.com/ReaJason/MemShellParty/compare/v1.3.2...v1.4.0)
## [v1.3.2](https://github.com/ReaJason/MemShellParty/releases/tag/v1.3.2) - 2025-02-25
### Fixed
- 修复 Tomcat WebSocket 注入报错,无法工作
### Changed
- 添加 foojay-toolchains 插件,支持 Dockerfile 构建时自动下载缺失的 JDK 版本
- 优化构建 Spring Boot 的 Dockerfile,最小权限原则
- 支持一键构建的 Dockerfile,适配需要 NGINX 反代的场景
- 代码重构支持一处注册所有 Server 的 Shell 配置
**Full Changelog:** [v1.3.1...v1.3.2](https://github.com/ReaJason/MemShellParty/compare/v1.3.1...v1.3.2)
## [v1.3.1](https://github.com/ReaJason/MemShellParty/releases/tag/v1.3.1) - 2025-02-20
### Added
- UI 中打包配置中添加 Loading 状态
### Fixed
- 修复 UI 在修改目标服务时,挂载类型有时未跟着变化导致生成失败
**Full Changelog:** [v1.3.0...v1.3.1](https://github.com/ReaJason/MemShellParty/compare/v1.3.0...v1.3.1)
## [v1.3.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.3.0) - 2025-02-20
### Added
- 支持 Hessian、Hessian2 反序列化,XSLT 链 (#36) by @ReaJason
### Changed
- 移除无用依赖,JavaSocketGson
- Gradle 升级至 8.12.1
- 更新 TestContainers 和 Junit 的版本
### Fixed
- 修复 UI 在仅修改打包方式重新生成时,多选 payload 下拉框置空,且 payload 没有变为最新的。
**Full Changelog:** [v1.2.1...v1.3.0](https://github.com/ReaJason/MemShellParty/compare/v1.2.1...v1.3.0)
## [v1.2.1](https://github.com/ReaJason/MemShellParty/releases/tag/v1.2.1) - 2025-02-19
### Changed
- UI 增强手机端响应式,增强 i18n 显示 (#39)
### Fixed
- 修复 CB110 版本 serialVersionUID 修改失效导致无法利用成功
**Full Changelog:** [v1.2.0...v1.2.1](https://github.com/ReaJason/MemShellParty/compare/v1.2.0...v1.2.1)
## [v1.2.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.2.0) - 2025-02-19
### Added
- 支持 AntSword 内存马生成 by @ReaJason
- 添加 Java 反序列化其他 CB 版本 Payload 生成
### Changed
- CI 分离单独测试 was7 集成测试,大幅度减少测试时间
- 部分 UI 调整
### Fixed
- 修复随机类名如果为保留字时会无法加载
**Full Changelog:** [v1.1.0...v1.2.0](https://github.com/ReaJason/MemShellParty/compare/v1.1.0...v1.2.0)
## [v1.1.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.1.0) - 2025-02-15
### Added
- 支持 Suo5 内存马生成 by @ReaJason
### Changed
- 升级 TailWind CSS v4
- 分离 i18n EN 和 ZH 为两个 json 文件,方便维护以及 VSCode 插件识别
### Fixed
- 修复 sonner 颜色主题未随着修改而变化
- 修复 IDEA 本地构建 version 一直是 unspecified
**Full Changelog:** [v1.0.0...v1.1.0](https://github.com/ReaJason/MemShellParty/compare/v1.0.0...v1.1.0)
## [v1.0.0](https://github.com/ReaJason/MemShellParty/releases/tag/v1.0.0) - 2025-01-03
### Added
- 支持 Tomcat、Jetty、WebLogic、GlassFish、JBoss、Resin 等 18 个中间件或框架的应用内存马
- 支持 Filter、Servlet、Listener、NettyHandler、Agent 等常见内存马挂载类型
- 支持哥斯拉、冰蝎、命令执行功能
- 支持 Base64、Jar、JSP、常见表达式、常见模板引擎、反序列化等打包方式
+9
View File
@@ -0,0 +1,9 @@
---
title: FQA
description: Getting Started with Fumadocs
icon: CircleAlert
---
Hey there! Fumadocs is the docs framework that also works on React Router!
## Heading
Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Some files were not shown because too many files have changed in this diff Show More