style: fmt code

This commit is contained in:
ReaJason
2026-04-26 20:39:53 +08:00
parent d9733e1512
commit a8fb030a60
150 changed files with 948 additions and 1522 deletions
+8 -16
View File
@@ -1,18 +1,14 @@
import type { VariantProps } from "class-variance-authority"; import type { VariantProps } from "class-variance-authority";
import { Check, Copy } from "lucide-react"; import { Check, Copy } from "lucide-react";
import { import { type HTMLProps, type ReactNode, useCallback, useEffect, useState } from "react";
type HTMLProps,
type ReactNode,
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 { 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/material-dark"; 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";
@@ -68,17 +64,13 @@ export default function CodeViewer({
showLineNumbers = true, showLineNumbers = true,
wrapLongLines = true, wrapLongLines = true,
}: Readonly<CodeViewerProps>) { }: Readonly<CodeViewerProps>) {
const lineProps: lineTagPropsFunction | HTMLProps<HTMLElement> | undefined = const lineProps: lineTagPropsFunction | HTMLProps<HTMLElement> | undefined = wrapLongLines
wrapLongLines ? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } }
? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } } : undefined;
: undefined;
return ( return (
<div className="rounded-lg border"> <div className="rounded-lg border">
<div <div
className={cn( className={cn("flex items-center justify-end border-b p-2", header && "justify-between")}
"flex items-center border-b p-2 justify-end",
header && "justify-between",
)}
> >
{header} {header}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -86,7 +78,7 @@ export default function CodeViewer({
<CopyButton value={code} variant="ghost" size="sm" /> <CopyButton value={code} variant="ghost" size="sm" />
</div> </div>
</div> </div>
<div className="relative overflow-hidden text-xs wrap-all"> <div className="wrap-all relative overflow-hidden text-xs">
<SyntaxHighlighter <SyntaxHighlighter
language={language} language={language}
style={materialDark} style={materialDark}
+4 -12
View File
@@ -1,13 +1,9 @@
import { Check, Copy } from "lucide-react"; import { Check, Copy } from "lucide-react";
import { import { type ComponentPropsWithoutRef, useCallback, useEffect, useState } from "react";
type ComponentPropsWithoutRef,
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";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -48,7 +44,7 @@ export function CopyableField({
return ( return (
<div className={cn("flex flex-col gap-1 py-1", className)} {...divProps}> <div className={cn("flex flex-col gap-1 py-1", className)} {...divProps}>
<div className="flex items-center justify-between gap-2 h-6"> <div className="flex h-6 items-center justify-between gap-2">
<Label className="text-sm text-muted-foreground">{label}</Label> <Label className="text-sm text-muted-foreground">{label}</Label>
{value && ( {value && (
<CopyToClipboard.CopyToClipboard text={value} onCopy={handleCopy}> <CopyToClipboard.CopyToClipboard text={value} onCopy={handleCopy}>
@@ -59,11 +55,7 @@ export function CopyableField({
className="h-8 w-8" className="h-8 w-8"
disabled={hasCopied} disabled={hasCopied}
> >
{hasCopied ? ( {hasCopied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
<Check className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
</Button> </Button>
</CopyToClipboard.CopyToClipboard> </CopyToClipboard.CopyToClipboard>
)} )}
+6 -13
View File
@@ -1,8 +1,10 @@
"use client"; "use client";
import { Image, type ImageProps } from "fumadocs-core/framework";
import type { ComponentProps } from "react"; import type { ComponentProps } from "react";
import { Image, type ImageProps } from "fumadocs-core/framework";
import Zoom, { type UncontrolledProps } from "react-medium-image-zoom"; import Zoom, { type UncontrolledProps } from "react-medium-image-zoom";
import "@/components/image-zoom.css"; import "@/components/image-zoom.css";
export type ImageZoomProps = ImageProps & { export type ImageZoomProps = ImageProps & {
@@ -22,20 +24,14 @@ function getImageSrc(src: ImageProps["src"]): string {
if (typeof src === "object") { if (typeof src === "object") {
// Next.js // Next.js
if ("default" in src) if ("default" in src) return (src as { default: { src: string } }).default.src;
return (src as { default: { src: string } }).default.src;
return src.src; return src.src;
} }
return ""; return "";
} }
export function ImageZoom({ export function ImageZoom({ zoomInProps, children, rmiz, ...props }: ImageZoomProps) {
zoomInProps,
children,
rmiz,
...props
}: ImageZoomProps) {
return ( return (
<Zoom <Zoom
zoomMargin={20} zoomMargin={20}
@@ -48,10 +44,7 @@ export function ImageZoom({
}} }}
> >
{children ?? ( {children ?? (
<Image <Image sizes="(max-width: 768px) 100vw, (max-width: 1200px) 70vw, 900px" {...props} />
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 70vw, 900px"
{...props}
/>
)} )}
</Zoom> </Zoom>
); );
+1
View File
@@ -1,5 +1,6 @@
import { LanguagesIcon } from "lucide-react"; import { LanguagesIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
export function LanguageSwitcher() { export function LanguageSwitcher() {
@@ -1,9 +1,9 @@
import { type MotionProps, motion } from "motion/react"; import { type MotionProps, motion } from "motion/react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
interface LineShadowTextProps interface LineShadowTextProps
extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps>, extends Omit<React.HTMLAttributes<HTMLElement>, keyof MotionProps>, MotionProps {
MotionProps {
shadowColor?: string; shadowColor?: string;
as?: React.ElementType; as?: React.ElementType;
} }
@@ -27,7 +27,7 @@ export function LineShadowText({
style={{ "--shadow-color": shadowColor } as React.CSSProperties} style={{ "--shadow-color": shadowColor } as React.CSSProperties}
className={cn( className={cn(
"relative z-0 inline-flex", "relative z-0 inline-flex",
"after:absolute after:left-[0.04em] after:top-[0.04em] after:content-[attr(data-text)]", "after:absolute after:top-[0.04em] after:left-[0.04em] after:content-[attr(data-text)]",
"after:bg-[linear-gradient(45deg,transparent_45%,var(--shadow-color)_45%,var(--shadow-color)_55%,transparent_0)]", "after:bg-[linear-gradient(45deg,transparent_45%,var(--shadow-color)_45%,var(--shadow-color)_55%,transparent_0)]",
"after:-z-10 after:bg-[length:0.06em_0.06em] after:bg-clip-text after:text-transparent", "after:-z-10 after:bg-[length:0.06em_0.06em] after:bg-clip-text after:text-transparent",
"after:animate-line-shadow", "after:animate-line-shadow",
+2 -1
View File
@@ -1,6 +1,7 @@
import defaultMdxComponents from "fumadocs-ui/mdx";
import type { MDXComponents } from "mdx/types"; import type { MDXComponents } from "mdx/types";
import defaultMdxComponents from "fumadocs-ui/mdx";
export function getMDXComponents(components?: MDXComponents) { export function getMDXComponents(components?: MDXComponents) {
return { return {
...defaultMdxComponents, ...defaultMdxComponents,
@@ -1,11 +1,9 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import {
Field, import { Field, FieldContent, FieldError, FieldLabel } from "@/components/ui/field";
FieldContent,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -13,7 +11,6 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import type { MemShellFormSchema } from "@/types/schema";
const JDKVersion = [ const JDKVersion = [
{ name: "Java6", value: "50" }, { name: "Java6", value: "50" },
@@ -37,9 +34,7 @@ export function JREVersionFormField({
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field orientation="vertical" data-invalid={fieldState.invalid}> <Field orientation="vertical" data-invalid={fieldState.invalid}>
<FieldContent> <FieldContent>
<FieldLabel htmlFor="targetJdkVersion"> <FieldLabel htmlFor="targetJdkVersion">{t("common:targetJdkVersion")}</FieldLabel>
{t("common:targetJdkVersion")}
</FieldLabel>
<Select <Select
onValueChange={(v) => { onValueChange={(v) => {
if (Number.parseInt(v ?? "0", 10) >= 53) { if (Number.parseInt(v ?? "0", 10) >= 53) {
@@ -51,13 +46,8 @@ export function JREVersionFormField({
}} }}
value={field.value} value={field.value}
> >
<SelectTrigger <SelectTrigger id="targetJdkVersion" aria-invalid={fieldState.invalid}>
id="targetJdkVersion" <SelectValue data-placeholder={t("common:placeholders.select")} />
aria-invalid={fieldState.invalid}
>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{JDKVersion.map((v) => ( {JDKVersion.map((v) => (
@@ -1,7 +1,10 @@
import type { MemShellFormSchema } from "@/types/schema";
import { ArrowUpRightIcon, InfoIcon, ServerIcon } from "lucide-react"; import { ArrowUpRightIcon, InfoIcon, ServerIcon } from "lucide-react";
import { useCallback, useEffect, useMemo } from "react"; import { useCallback, useEffect, useMemo } from "react";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { AntSwordTabContent } from "@/components/memshell/tabs/antsword-tab"; import { AntSwordTabContent } from "@/components/memshell/tabs/antsword-tab";
import { BehinderTabContent } from "@/components/memshell/tabs/behinder-tab"; import { BehinderTabContent } from "@/components/memshell/tabs/behinder-tab";
import { CommandTabContent } from "@/components/memshell/tabs/command-tab"; import { CommandTabContent } from "@/components/memshell/tabs/command-tab";
@@ -10,12 +13,7 @@ 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 { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { import { Field, FieldContent, FieldDescription, FieldLabel } from "@/components/ui/field";
Field,
FieldContent,
FieldDescription,
FieldLabel,
} from "@/components/ui/field";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { import {
Select, Select,
@@ -26,17 +24,9 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Tabs } from "@/components/ui/tabs"; import { Tabs } from "@/components/ui/tabs";
import { import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
Tooltip, import { type MainConfig, type ServerConfig, ShellToolType } from "@/types/memshell";
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
type MainConfig,
type ServerConfig,
ShellToolType,
} from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema";
import { Spinner } from "../ui/spinner"; import { Spinner } from "../ui/spinner";
import { JREVersionFormField } from "./jreversion-field"; import { JREVersionFormField } from "./jreversion-field";
import { ServerVersionFormField } from "./serverversion-field"; import { ServerVersionFormField } from "./serverversion-field";
@@ -76,9 +66,7 @@ export default function MainConfigCard({
if (!serverToolMap) { if (!serverToolMap) {
return []; return [];
} }
const tools = Object.keys(serverToolMap).map( const tools = Object.keys(serverToolMap).map((tool) => tool as ShellToolType);
(tool) => tool as ShellToolType,
);
return Array.from(new Set([...tools, ShellToolType.Custom])); return Array.from(new Set([...tools, ShellToolType.Custom]));
}, [serverToolMap]); }, [serverToolMap]);
@@ -134,9 +122,7 @@ export default function MainConfigCard({
const currentTargetJdk = form.getValues("targetJdkVersion") as string; const currentTargetJdk = form.getValues("targetJdkVersion") as string;
const currentJdkVersion = Number.parseInt(currentTargetJdk, 10); const currentJdkVersion = Number.parseInt(currentTargetJdk, 10);
const shouldRaiseJdkVersion = const shouldRaiseJdkVersion =
(server === "SpringWebFlux" || (server === "SpringWebFlux" || server === "XXLJOB" || server === "Dubbo") &&
server === "XXLJOB" ||
server === "Dubbo") &&
currentJdkVersion <= 52; currentJdkVersion <= 52;
const nextJdkVersion = shouldRaiseJdkVersion ? "52" : "50"; const nextJdkVersion = shouldRaiseJdkVersion ? "52" : "50";
if (currentTargetJdk !== nextJdkVersion) { if (currentTargetJdk !== nextJdkVersion) {
@@ -247,39 +233,27 @@ export default function MainConfigCard({
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{!mainConfig ? ( {!mainConfig ? (
<div className="flex items-center justify-center p-4 gap-4 h-100"> <div className="flex h-100 items-center justify-center gap-4 p-4">
<Spinner /> <Spinner />
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">{t("loading")}</span>
{t("loading")}
</span>
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller <Controller
control={form.control} control={form.control}
name="server" name="server"
render={({ field }) => ( render={({ field }) => (
<Field> <Field>
<FieldContent> <FieldContent>
<FieldLabel htmlFor="server"> <FieldLabel htmlFor="server">{t("common:server")}</FieldLabel>
{t("common:server")} <Select onValueChange={field.onChange} value={field.value}>
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<SelectTrigger id="server"> <SelectTrigger id="server">
<SelectValue <SelectValue data-placeholder={t("common:placeholders.select")} />
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{serverOptions.map((serverOption) => ( {serverOptions.map((serverOption) => (
<SelectItem <SelectItem key={serverOption} value={serverOption}>
key={serverOption}
value={serverOption}
>
{serverOption} {serverOption}
</SelectItem> </SelectItem>
))} ))}
@@ -303,26 +277,20 @@ export default function MainConfigCard({
/> />
<ServerVersionFormField form={form} /> <ServerVersionFormField form={form} />
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller <Controller
control={form.control} control={form.control}
name="shellTool" name="shellTool"
render={({ field }) => ( render={({ field }) => (
<Field> <Field>
<FieldContent> <FieldContent>
<FieldLabel htmlFor="shellTool"> <FieldLabel htmlFor="shellTool">{t("common:shellTool")}</FieldLabel>
{t("common:shellTool")}
</FieldLabel>
<Select <Select
value={field.value} value={field.value}
onValueChange={(v) => onValueChange={(v) => handleShellToolChange(v as string)}
handleShellToolChange(v as string)
}
> >
<SelectTrigger id="shellTool"> <SelectTrigger id="shellTool">
<SelectValue <SelectValue data-placeholder={t("common:placeholders.select")} />
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{shellTools.map((tool) => ( {shellTools.map((tool) => (
@@ -338,21 +306,17 @@ export default function MainConfigCard({
/> />
<JREVersionFormField form={form} /> <JREVersionFormField form={form} />
</div> </div>
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3"> <div className="mt-4 flex flex-col gap-4 lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<Controller <Controller
control={form.control} control={form.control}
name="debug" name="debug"
render={({ field }) => ( render={({ field }) => (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Switch <Switch id="debug" checked={field.value} onCheckedChange={field.onChange} />
id="debug"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="debug">{t("common:debug")}</Label> <Label htmlFor="debug">{t("common:debug")}</Label>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" /> <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>{t("common:debug.description")}</p> <p>{t("common:debug.description")}</p>
@@ -366,15 +330,11 @@ export default function MainConfigCard({
name="probe" name="probe"
render={({ field }) => ( render={({ field }) => (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Switch <Switch id="probe" checked={field.value} onCheckedChange={field.onChange} />
id="probe"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="probe">{t("common:probe")}</Label> <Label htmlFor="probe">{t("common:probe")}</Label>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" /> <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>{t("common:probe.description")}</p> <p>{t("common:probe.description")}</p>
@@ -388,17 +348,11 @@ export default function MainConfigCard({
name="byPassJavaModule" name="byPassJavaModule"
render={({ field }) => ( render={({ field }) => (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Switch <Switch id="bypass" checked={field.value} onCheckedChange={field.onChange} />
id="bypass" <Label htmlFor="bypass">{t("common:byPassJavaModule")}</Label>
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="bypass">
{t("common:byPassJavaModule")}
</Label>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" /> <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>{t("common:byPassJavaModule.description")}</p> <p>{t("common:byPassJavaModule.description")}</p>
@@ -417,12 +371,10 @@ export default function MainConfigCard({
checked={field.value} checked={field.value}
onCheckedChange={field.onChange} onCheckedChange={field.onChange}
/> />
<Label htmlFor="lambdaSuffix"> <Label htmlFor="lambdaSuffix">{t("common:lambdaSuffix")}</Label>
{t("common:lambdaSuffix")}
</Label>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" /> <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>{t("common:lambdaSuffix.description")}</p> <p>{t("common:lambdaSuffix.description")}</p>
@@ -436,15 +388,11 @@ export default function MainConfigCard({
name="shrink" name="shrink"
render={({ field }) => ( render={({ field }) => (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Switch <Switch id="shrink" checked={field.value} onCheckedChange={field.onChange} />
id="shrink"
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor="shrink">{t("common:shrink")}</Label> <Label htmlFor="shrink">{t("common:shrink")}</Label>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" /> <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>{t("common:shrink.description")}</p> <p>{t("common:shrink.description")}</p>
@@ -463,12 +411,10 @@ export default function MainConfigCard({
checked={field.value} checked={field.value}
onCheckedChange={field.onChange} onCheckedChange={field.onChange}
/> />
<Label htmlFor="staticInitialize"> <Label htmlFor="staticInitialize">{t("common:staticInitialize")}</Label>
{t("common:staticInitialize")}
</Label>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" /> <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>{t("common:staticInitialize.description")}</p> <p>{t("common:staticInitialize.description")}</p>
@@ -489,11 +435,7 @@ export default function MainConfigCard({
<BehinderTabContent form={form} shellTypes={shellTypes} /> <BehinderTabContent form={form} shellTypes={shellTypes} />
<AntSwordTabContent form={form} shellTypes={shellTypes} /> <AntSwordTabContent form={form} shellTypes={shellTypes} />
<Suo5TabContent tabValue="Suo5" form={form} shellTypes={shellTypes} /> <Suo5TabContent tabValue="Suo5" form={form} shellTypes={shellTypes} />
<Suo5TabContent <Suo5TabContent tabValue="Suo5v2" form={form} shellTypes={shellTypes} />
tabValue="Suo5v2"
form={form}
shellTypes={shellTypes}
/>
<NeoRegTabContent form={form} shellTypes={shellTypes} /> <NeoRegTabContent form={form} shellTypes={shellTypes} />
<CustomTabContent form={form} shellTypes={shellTypes} /> <CustomTabContent form={form} shellTypes={shellTypes} />
<ProxyTabContent form={form} shellTypes={shellTypes} /> <ProxyTabContent form={form} shellTypes={shellTypes} />
@@ -1,13 +1,15 @@
import type { PackerConfig } from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema";
import { PackageIcon } from "lucide-react"; import { PackageIcon } from "lucide-react";
import { useMemo } from "react"; import { useMemo } from "react";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { FieldLabel, FieldSet } from "@/components/ui/field"; import { FieldLabel, FieldSet } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import type { PackerConfig } from "@/types/memshell";
import type { MemShellFormSchema } from "@/types/schema";
export default function PackageConfigCard({ export default function PackageConfigCard({
packerConfig, packerConfig,
@@ -84,11 +86,9 @@ export default function PackageConfigCard({
)} )}
/> />
) : ( ) : (
<div className="flex items-center justify-center p-4 gap-4 h-50"> <div className="flex h-50 items-center justify-center gap-4 p-4">
<Spinner /> <Spinner />
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">{t("loading")}</span>
{t("loading")}
</span>
</div> </div>
)} )}
</CardContent> </CardContent>
+2 -1
View File
@@ -1,5 +1,6 @@
import { ScrollTextIcon } from "lucide-react"; import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export function QuickUsage() { export function QuickUsage() {
@@ -13,7 +14,7 @@ export function QuickUsage() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ol className="flex flex-col gap-4 list-decimal list-inside text-sm"> <ol className="flex list-inside list-decimal flex-col gap-4 text-sm">
<li>{t("memshell:quickUsage.step1")}</li> <li>{t("memshell:quickUsage.step1")}</li>
<li>{t("memshell:quickUsage.step2")}</li> <li>{t("memshell:quickUsage.step2")}</li>
<li>{t("memshell:quickUsage.step3")}</li> <li>{t("memshell:quickUsage.step3")}</li>
+7 -10
View File
@@ -1,10 +1,12 @@
import type { MemShellResult } from "@/types/memshell";
import { ScrollTextIcon } from "lucide-react"; import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { downloadBytes, formatBytes } from "@/lib/utils"; import { downloadBytes, formatBytes } from "@/lib/utils";
import type { MemShellResult } from "@/types/memshell";
export function AgentResult({ export function AgentResult({
packMethod, packMethod,
@@ -26,11 +28,10 @@ export function AgentResult({
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm"> <ol className="list-inside list-decimal space-y-4 text-sm">
<li className="flex items-center justify-between"> <li className="flex items-center justify-between">
<span> <span>
{t("common:download")} MemShellAgent.jar ( {t("common:download")} MemShellAgent.jar ({formatBytes(atob(packResult).length)})
{formatBytes(atob(packResult).length)})
</span> </span>
<Button <Button
size="sm" size="sm"
@@ -56,9 +57,7 @@ export function AgentResult({
variant="outline" variant="outline"
className="w-28" className="w-28"
type="button" type="button"
onClick={() => onClick={() => window.open("https://github.com/jattach/jattach/releases")}
window.open("https://github.com/jattach/jattach/releases")
}
> >
{t("common:download")} {t("common:download")}
</Button> </Button>
@@ -72,9 +71,7 @@ export function AgentResult({
</li> </li>
<li>{t("memshell:tips.get-pid")}</li> <li>{t("memshell:tips.get-pid")}</li>
<li> <li>
{isPureAgent {isPureAgent ? t("memshell:tips.execute-command") : t("memshell:tips.execute-command1")}
? t("memshell:tips.execute-command")
: t("memshell:tips.execute-command1")}
</li> </li>
<li>{t("memshell:tips.try-to-use-shell")}</li> <li>{t("memshell:tips.try-to-use-shell")}</li>
</ol> </ol>
@@ -1,6 +1,7 @@
import { FileTextIcon } from "lucide-react"; import { FileTextIcon } from "lucide-react";
import { Fragment } from "react/jsx-runtime";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Fragment } from "react/jsx-runtime";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { notNeedUrlPattern } from "@/lib/utils"; import { notNeedUrlPattern } from "@/lib/utils";
@@ -15,12 +16,11 @@ import {
ShellToolType, ShellToolType,
type Suo5ShellToolConfig, type Suo5ShellToolConfig,
} from "@/types/memshell"; } from "@/types/memshell";
import { CopyableField } from "../../copyable-field"; import { CopyableField } from "../../copyable-field";
import { FeedbackAlert } from "./feedback-alert"; import { FeedbackAlert } from "./feedback-alert";
export function BasicInfo({ export function BasicInfo({ generateResult }: Readonly<{ generateResult?: MemShellResult }>) {
generateResult,
}: Readonly<{ generateResult?: MemShellResult }>) {
const { t } = useTranslation(["memshell", "common"]); const { t } = useTranslation(["memshell", "common"]);
const isDubbo = generateResult?.shellConfig.server === "Dubbo"; const isDubbo = generateResult?.shellConfig.server === "Dubbo";
return ( return (
@@ -35,11 +35,8 @@ export function BasicInfo({
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<CopyableField <CopyableField label={t("common:server")} text={generateResult?.shellConfig.server} />
label={t("common:server")}
text={generateResult?.shellConfig.server}
/>
<CopyableField <CopyableField
label={t("mainConfig.shellTool")} label={t("mainConfig.shellTool")}
text={generateResult?.shellConfig.shellTool} text={generateResult?.shellConfig.shellTool}
@@ -55,29 +52,21 @@ export function BasicInfo({
value={generateResult?.injectorConfig.urlPattern} value={generateResult?.injectorConfig.urlPattern}
/> />
</div> </div>
{generateResult?.shellConfig.shellTool !== ShellToolType.Custom && {generateResult?.shellConfig.shellTool !== ShellToolType.Custom && !isDubbo && (
!isDubbo && <Separator className="my-1" />} <Separator className="my-1" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> )}
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
{generateResult?.shellConfig.shellTool === ShellToolType.Behinder && ( {generateResult?.shellConfig.shellTool === ShellToolType.Behinder && (
<> <>
<CopyableField <CopyableField label={t("shellToolConfig.behinderScriptType")} text="jsp" />
label={t("shellToolConfig.behinderScriptType")}
text="jsp"
/>
<CopyableField <CopyableField
label={t("shellToolConfig.behinderEncryptType")} label={t("shellToolConfig.behinderEncryptType")}
text={t("shellToolConfig.behinderDefaultEncryptType")} text={t("shellToolConfig.behinderDefaultEncryptType")}
/> />
<CopyableField <CopyableField
label={t("shellToolConfig.behinder.pass")} label={t("shellToolConfig.behinder.pass")}
text={ text={(generateResult?.shellToolConfig as BehinderShellToolConfig).pass}
(generateResult?.shellToolConfig as BehinderShellToolConfig) value={(generateResult?.shellToolConfig as BehinderShellToolConfig).pass}
.pass
}
value={
(generateResult?.shellToolConfig as BehinderShellToolConfig)
.pass
}
/> />
<CopyableField <CopyableField
label={t("shellToolConfig.behinder.header")} label={t("shellToolConfig.behinder.header")}
@@ -90,25 +79,13 @@ export function BasicInfo({
<> <>
<CopyableField <CopyableField
label={t("shellToolConfig.godzilla.pass")} label={t("shellToolConfig.godzilla.pass")}
text={ text={(generateResult?.shellToolConfig as GodzillaShellToolConfig).pass}
(generateResult?.shellToolConfig as GodzillaShellToolConfig) value={(generateResult?.shellToolConfig as GodzillaShellToolConfig).pass}
.pass
}
value={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.pass
}
/> />
<CopyableField <CopyableField
label={t("shellToolConfig.godzilla.key")} label={t("shellToolConfig.godzilla.key")}
text={ text={(generateResult?.shellToolConfig as GodzillaShellToolConfig).key}
(generateResult?.shellToolConfig as GodzillaShellToolConfig) value={(generateResult?.shellToolConfig as GodzillaShellToolConfig).key}
.key
}
value={
(generateResult?.shellToolConfig as GodzillaShellToolConfig)
.key
}
/> />
<CopyableField <CopyableField
label={t("shellToolConfig.godzilla.encryptor")} label={t("shellToolConfig.godzilla.encryptor")}
@@ -125,38 +102,27 @@ export function BasicInfo({
/> />
</> </>
)} )}
{generateResult?.shellConfig.shellTool === ShellToolType.Command && {generateResult?.shellConfig.shellTool === ShellToolType.Command && !isDubbo && (
!isDubbo && ( <Fragment>
<Fragment> <CopyableField
<CopyableField hidden={generateResult?.shellConfig.shellType.includes("WebSocket")}
hidden={generateResult?.shellConfig.shellType.includes( label={t("common:paramName")}
"WebSocket", text={(generateResult?.shellToolConfig as CommandShellToolConfig).paramName}
)} value={(generateResult?.shellToolConfig as CommandShellToolConfig).paramName}
label={t("common:paramName")} />
text={ <CopyableField
(generateResult?.shellToolConfig as CommandShellToolConfig) hidden={
.paramName !(
} generateResult?.shellConfig.shellType === "BypassNginxWebSocket" ||
value={ generateResult?.shellConfig.shellType === "BypassNginxJakartaWebSocket"
(generateResult?.shellToolConfig as CommandShellToolConfig) )
.paramName }
} label={t("shellToolConfig.httpHeader")}
/> text={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
<CopyableField value={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
hidden={ />
!( </Fragment>
generateResult?.shellConfig.shellType === )}
"BypassNginxWebSocket" ||
generateResult?.shellConfig.shellType ===
"BypassNginxJakartaWebSocket"
)
}
label={t("shellToolConfig.httpHeader")}
text={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
value={`${(generateResult?.shellToolConfig as CommandShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as CommandShellToolConfig).headerValue}`}
/>
</Fragment>
)}
{(generateResult?.shellConfig.shellTool === ShellToolType.Suo5 || {(generateResult?.shellConfig.shellTool === ShellToolType.Suo5 ||
generateResult?.shellConfig.shellTool === ShellToolType.Suo5v2) && ( generateResult?.shellConfig.shellTool === ShellToolType.Suo5v2) && (
<CopyableField <CopyableField
@@ -176,14 +142,8 @@ export function BasicInfo({
<> <>
<CopyableField <CopyableField
label={t("shellToolConfig.antSword.pass")} label={t("shellToolConfig.antSword.pass")}
text={ text={(generateResult?.shellToolConfig as AntSwordShellToolConfig).pass}
(generateResult?.shellToolConfig as AntSwordShellToolConfig) value={(generateResult?.shellToolConfig as AntSwordShellToolConfig).pass}
.pass
}
value={
(generateResult?.shellToolConfig as AntSwordShellToolConfig)
.pass
}
/> />
<CopyableField <CopyableField
label={t("shellToolConfig.httpHeader")} label={t("shellToolConfig.httpHeader")}
@@ -192,14 +152,9 @@ export function BasicInfo({
/> />
</> </>
)} )}
{generateResult?.shellConfig.shellTool === {generateResult?.shellConfig.shellTool === ShellToolType.NeoreGeorg && (
ShellToolType.NeoreGeorg && (
<> <>
<CopyableField <CopyableField label={t("shellToolConfig.neoreGeorgKey")} text="key" value="key" />
label={t("shellToolConfig.neoreGeorgKey")}
text="key"
value="key"
/>
<CopyableField <CopyableField
label={t("shellToolConfig.neoreGeorgHeader")} label={t("shellToolConfig.neoreGeorgHeader")}
text={`${(generateResult?.shellToolConfig as NeoreGeorgShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as NeoreGeorgShellToolConfig).headerValue}`} text={`${(generateResult?.shellToolConfig as NeoreGeorgShellToolConfig).headerName}: ${(generateResult?.shellToolConfig as NeoreGeorgShellToolConfig).headerValue}`}
@@ -209,7 +164,7 @@ export function BasicInfo({
)} )}
</div> </div>
<Separator className="my-1" /> <Separator className="my-1" />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<CopyableField <CopyableField
label={t("mainConfig.injectorClassName")} label={t("mainConfig.injectorClassName")}
value={generateResult?.injectorClassName} value={generateResult?.injectorClassName}
@@ -1,5 +1,6 @@
import { CircleHelpIcon } from "lucide-react"; import { CircleHelpIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
AlertDialog, AlertDialog,
AlertDialogAction, AlertDialogAction,
@@ -1,10 +1,12 @@
import type { MemShellResult } from "@/types/memshell";
import { ScrollTextIcon } from "lucide-react"; import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { downloadBytes, formatBytes } from "@/lib/utils"; import { downloadBytes, formatBytes } from "@/lib/utils";
import type { MemShellResult } from "@/types/memshell";
export function JarResult({ export function JarResult({
packMethod, packMethod,
@@ -25,11 +27,10 @@ export function JarResult({
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm"> <ol className="list-inside list-decimal space-y-4 text-sm">
<li className="flex items-center justify-between"> <li className="flex items-center justify-between">
<span> <span>
{t("common:download")} {packMethod}Shell.jar ( {t("common:download")} {packMethod}Shell.jar ({formatBytes(atob(packResult).length)})
{formatBytes(atob(packResult).length)})
</span> </span>
<Button <Button
size="sm" size="sm"
@@ -1,6 +1,7 @@
import { DownloadIcon } from "lucide-react"; import { DownloadIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CodeViewer from "@/components/code-viewer"; import CodeViewer from "@/components/code-viewer";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@@ -26,14 +27,9 @@ export function MultiPackResult({
const showCode = packMethod === "JSP"; const showCode = packMethod === "JSP";
const { t } = useTranslation(); const { t } = useTranslation();
const packResults = allPackResults as Record<string, string> | undefined; const packResults = allPackResults as Record<string, string> | undefined;
const packMethods = useMemo( const packMethods = useMemo(() => Object.keys(packResults ?? {}), [packResults]);
() => Object.keys(packResults ?? {}),
[packResults],
);
const [selectedMethod, setSelectedMethod] = useState( const [selectedMethod, setSelectedMethod] = useState(() => packMethods[0] ?? "");
() => packMethods[0] ?? "",
);
const packResult = useMemo(() => { const packResult = useMemo(() => {
if (!selectedMethod) { if (!selectedMethod) {
@@ -55,16 +51,12 @@ export function MultiPackResult({
}, [packMethods, selectedMethod]); }, [packMethods, selectedMethod]);
const handleDownload = useCallback(() => { const handleDownload = useCallback(() => {
const fileName = const fileName = shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
if (packMethod === "JSP") { if (packMethod === "JSP") {
const fileExtension = selectedMethod.includes("JSPX") ? ".jspx" : ".jsp"; const fileExtension = selectedMethod.includes("JSPX") ? ".jspx" : ".jsp";
const content = new Blob([packResult], { type: "text/plain" }); const content = new Blob([packResult], { type: "text/plain" });
return downloadContent(content, fileName, fileExtension); return downloadContent(content, fileName, fileExtension);
} else if ( } else if (packMethod === "JavaDeserialize" || packMethod.includes("Hessian")) {
packMethod === "JavaDeserialize" ||
packMethod.includes("Hessian")
) {
const content = new Blob([base64ToBytes(packResult)], { const content = new Blob([base64ToBytes(packResult)], {
type: "application/octet-stream", type: "application/octet-stream",
}); });
@@ -73,20 +65,13 @@ export function MultiPackResult({
const base64Content = packResults?.[packMethods[0]] ?? ""; const base64Content = packResults?.[packMethods[0]] ?? "";
return downloadBytes(base64Content, shellClassName); return downloadBytes(base64Content, shellClassName);
} }
}, [ }, [packMethod, packMethods, packResult, packResults, selectedMethod, shellClassName]);
packMethod,
packMethods,
packResult,
packResults,
selectedMethod,
shellClassName,
]);
return ( return (
<CodeViewer <CodeViewer
code={packResult ?? ""} code={packResult ?? ""}
header={ header={
<div className="flex items-center justify-between text-xs gap-2"> <div className="flex items-center justify-between gap-2 text-xs">
<Select <Select
onValueChange={(value) => { onValueChange={(value) => {
setSelectedMethod(value as string); setSelectedMethod(value as string);
@@ -94,9 +79,7 @@ export function MultiPackResult({
value={selectedMethod} value={selectedMethod}
> >
<SelectTrigger className="h-7 text-xs [&_svg]:h-4 [&_svg]:w-4"> <SelectTrigger className="h-7 text-xs [&_svg]:h-4 [&_svg]:w-4">
<span className="text-muted-foreground"> <span className="text-muted-foreground">{t("common:packerMethod")}:&nbsp;</span>
{t("common:packerMethod")}:&nbsp;
</span>
<SelectValue data-placeholder={t("common:placeholders.select")} /> <SelectValue data-placeholder={t("common:placeholders.select")} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -1,13 +1,16 @@
import { useTranslation } from "react-i18next";
import CodeViewer from "@/components/code-viewer";
import type { MemShellResult } from "@/types/memshell"; import type { MemShellResult } from "@/types/memshell";
import { DownloadIcon } from "lucide-react";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import CodeViewer from "@/components/code-viewer";
import { Button } from "@/components/ui/button";
import { base64ToBytes, downloadBytes, downloadContent } from "@/lib/utils";
import { AgentResult } from "./agent"; import { AgentResult } from "./agent";
import { JarResult } from "./jar-result"; import { JarResult } from "./jar-result";
import { MultiPackResult } from "./multi-packer"; import { MultiPackResult } from "./multi-packer";
import { useCallback } from "react";
import { base64ToBytes, downloadBytes, downloadContent } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { DownloadIcon } from "lucide-react";
export function ResultComponent({ export function ResultComponent({
packResult, packResult,
@@ -55,16 +58,12 @@ export function ResultComponent({
const shellClassName = generateResult?.shellClassName; const shellClassName = generateResult?.shellClassName;
const handleDownload = useCallback(() => { const handleDownload = useCallback(() => {
const fileName = const fileName = shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
shellClassName?.substring(shellClassName?.lastIndexOf(".") ?? 0) ?? "";
if (packMethod.includes("JSP")) { if (packMethod.includes("JSP")) {
const fileExtension = packMethod.includes("JSPX") ? ".jspx" : ".jsp"; const fileExtension = packMethod.includes("JSPX") ? ".jspx" : ".jsp";
const content = new Blob([packResult as string], { type: "text/plain" }); const content = new Blob([packResult as string], { type: "text/plain" });
return downloadContent(content, fileName, fileExtension); return downloadContent(content, fileName, fileExtension);
} else if ( } else if (packMethod.includes("JavaCommons") || packMethod.includes("Hessian")) {
packMethod.includes("JavaCommons") ||
packMethod.includes("Hessian")
) {
const content = new Blob([base64ToBytes(packResult as string)], { const content = new Blob([base64ToBytes(packResult as string)], {
type: "application/octet-stream", type: "application/octet-stream",
}); });
@@ -78,7 +77,7 @@ export function ResultComponent({
<CodeViewer <CodeViewer
code={packResult ?? ""} code={packResult ?? ""}
header={ header={
<div className="flex items-center justify-between text-xs gap-2"> <div className="flex items-center justify-between gap-2 text-xs">
<span> <span>
{t("common:packerMethod")}{packMethod} {t("common:packerMethod")}{packMethod}
</span> </span>
@@ -1,11 +1,9 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import {
Field, import { Field, FieldContent, FieldError, FieldLabel } from "@/components/ui/field";
FieldContent,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -13,7 +11,6 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import type { MemShellFormSchema } from "@/types/schema";
export function ServerVersionFormField({ export function ServerVersionFormField({
form, form,
@@ -30,17 +27,10 @@ export function ServerVersionFormField({
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field orientation="vertical" data-invalid={fieldState.invalid}> <Field orientation="vertical" data-invalid={fieldState.invalid}>
<FieldContent> <FieldContent>
<FieldLabel htmlFor="serverVersion"> <FieldLabel htmlFor="serverVersion">{t("common:serverVersion")}</FieldLabel>
{t("common:serverVersion")}
</FieldLabel>
<Select onValueChange={field.onChange} value={field.value}> <Select onValueChange={field.onChange} value={field.value}>
<SelectTrigger <SelectTrigger id="serverVersion" aria-invalid={fieldState.invalid}>
id="serverVersion" <SelectValue data-placeholder={t("common:placeholders.select")} />
aria-invalid={fieldState.invalid}
>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{serverVersionOptions.map((v) => ( {serverVersionOptions.map((v) => (
+8 -19
View File
@@ -1,3 +1,5 @@
import type { MemShellResult } from "@/types/memshell";
import { DownloadIcon } from "lucide-react"; import { DownloadIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -6,7 +8,7 @@ import { QuickUsage } from "@/components/memshell/quick-usage";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { downloadBytes } from "@/lib/utils"; import { downloadBytes } from "@/lib/utils";
import type { MemShellResult } from "@/types/memshell";
import CodeViewer from "../code-viewer"; import CodeViewer from "../code-viewer";
import { BasicInfo } from "./results/basic-info"; import { BasicInfo } from "./results/basic-info";
import { ResultComponent } from "./results/result-component"; import { ResultComponent } from "./results/result-component";
@@ -30,13 +32,9 @@ export default function ShellResult({
return ( return (
<Tabs defaultValue="packResult"> <Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-3"> <TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="packResult"> <TabsTrigger value="packResult">{t("common:generateResult")}</TabsTrigger>
{t("common:generateResult")}
</TabsTrigger>
<TabsTrigger value="shell">{t("memshell:shellClass")}</TabsTrigger> <TabsTrigger value="shell">{t("memshell:shellClass")}</TabsTrigger>
<TabsTrigger value="injector"> <TabsTrigger value="injector">{t("memshell:injectorClass")}</TabsTrigger>
{t("memshell:injectorClass")}
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="packResult" className="space-y-2"> <TabsContent value="packResult" className="space-y-2">
<BasicInfo generateResult={generateResult} /> <BasicInfo generateResult={generateResult} />
@@ -50,11 +48,7 @@ export default function ShellResult({
<TabsContent value="shell" className="mt-4"> <TabsContent value="shell" className="mt-4">
<CodeViewer <CodeViewer
showLineNumbers={false} showLineNumbers={false}
header={ header={<div className="truncate text-xs">{generateResult?.shellClassName}</div>}
<div className="text-xs truncate">
{generateResult?.shellClassName}
</div>
}
button={ button={
<Button <Button
variant="ghost" variant="ghost"
@@ -66,10 +60,7 @@ export default function ShellResult({
toast.warning(t("memshell:tips.shellBytesEmpty")); toast.warning(t("memshell:tips.shellBytesEmpty"));
return; return;
} }
downloadBytes( downloadBytes(generateResult?.shellBytesBase64Str, generateResult?.shellClassName);
generateResult?.shellBytesBase64Str,
generateResult?.shellClassName,
);
}} }}
> >
<DownloadIcon className="h-4 w-4" /> <DownloadIcon className="h-4 w-4" />
@@ -85,9 +76,7 @@ export default function ShellResult({
<CodeViewer <CodeViewer
showLineNumbers={false} showLineNumbers={false}
wrapLongLines={true} wrapLongLines={true}
header={ header={<div className="text-xs">{generateResult?.injectorClassName}</div>}
<div className="text-xs">{generateResult?.injectorClassName}</div>
}
button={ button={
<Button <Button
variant="ghost" variant="ghost"
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field"; import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field"; import { ShellTypeFormField } from "./shelltype-field";
@@ -19,7 +22,7 @@ export function AntSwordTabContent({
return ( return (
<TabsContent value="AntSword"> <TabsContent value="AntSword">
<Card> <Card>
<CardContent className="space-y-2 mt-4"> <CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} /> <ShellTypeFormField form={form} shellTypes={shellTypes} />
<Controller <Controller
control={form.control} control={form.control}
@@ -29,24 +32,18 @@ export function AntSwordTabContent({
<FieldLabel> <FieldLabel>
{t("shellToolConfig.antSword.pass")} {t("common:optional")} {t("shellToolConfig.antSword.pass")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller <Controller
control={form.control} control={form.control}
name="headerName" name="headerName"
render={({ field }) => ( render={({ field }) => (
<Field className="gap-1"> <Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel> <FieldLabel>{t("common:headerName")}</FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -58,10 +55,7 @@ export function AntSwordTabContent({
<FieldLabel> <FieldLabel>
{t("common:headerValue")} {t("common:optional")} {t("common:headerValue")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field"; import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field"; import { ShellTypeFormField } from "./shelltype-field";
@@ -19,7 +22,7 @@ export function BehinderTabContent({
return ( return (
<TabsContent value="Behinder"> <TabsContent value="Behinder">
<Card> <Card>
<CardContent className="space-y-2 mt-4"> <CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} /> <ShellTypeFormField form={form} shellTypes={shellTypes} />
<Controller <Controller
control={form.control} control={form.control}
@@ -29,24 +32,18 @@ export function BehinderTabContent({
<FieldLabel> <FieldLabel>
{t("shellToolConfig.behinder.pass")} {t("common:optional")} {t("shellToolConfig.behinder.pass")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller <Controller
control={form.control} control={form.control}
name="headerName" name="headerName"
render={({ field }) => ( render={({ field }) => (
<Field className="gap-1"> <Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel> <FieldLabel>{t("common:headerName")}</FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -58,10 +55,7 @@ export function BehinderTabContent({
<FieldLabel> <FieldLabel>
{t("common:headerValue")} {t("common:optional")} {t("common:headerValue")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -1,11 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Shuffle } from "lucide-react"; import { Shuffle } from "lucide-react";
import { Fragment, useEffect, useState } from "react"; import { Fragment, useEffect, useState } from "react";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Field, FieldLabel } from "@/components/ui/field"; import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import type { MemShellFormSchema } from "@/types/schema";
export function OptionalClassFormField({ export function OptionalClassFormField({
form, form,
@@ -16,12 +18,8 @@ export function OptionalClassFormField({
const [useRandomClassName, setUseRandomClassName] = useState( const [useRandomClassName, setUseRandomClassName] = useState(
() => !(initialShellClassName?.trim() || initialInjectorClassName?.trim()), () => !(initialShellClassName?.trim() || initialInjectorClassName?.trim()),
); );
const [savedShellClassName, setSavedShellClassName] = useState( const [savedShellClassName, setSavedShellClassName] = useState(initialShellClassName);
initialShellClassName, const [savedInjectorClassName, setSavedInjectorClassName] = useState(initialInjectorClassName);
);
const [savedInjectorClassName, setSavedInjectorClassName] = useState(
initialInjectorClassName,
);
const shellClassName = form.watch("shellClassName"); const shellClassName = form.watch("shellClassName");
const injectorClassName = form.watch("injectorClassName"); const injectorClassName = form.watch("injectorClassName");
@@ -38,10 +36,7 @@ export function OptionalClassFormField({
}, [injectorClassName, useRandomClassName]); }, [injectorClassName, useRandomClassName]);
useEffect(() => { useEffect(() => {
if ( if (useRandomClassName && (shellClassName?.trim() || injectorClassName?.trim())) {
useRandomClassName &&
(shellClassName?.trim() || injectorClassName?.trim())
) {
setUseRandomClassName(false); setUseRandomClassName(false);
} }
}, [injectorClassName, shellClassName, useRandomClassName]); }, [injectorClassName, shellClassName, useRandomClassName]);
@@ -61,7 +56,7 @@ export function OptionalClassFormField({
return ( return (
<Fragment> <Fragment>
<div className="pt-2 flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3 pt-2">
<div className="flex items-center gap-2 text-sm font-medium"> <div className="flex items-center gap-2 text-sm font-medium">
<Shuffle className="h-4 w-4" /> <Shuffle className="h-4 w-4" />
<span>{t("mainConfig.randomClassName")}</span> <span>{t("mainConfig.randomClassName")}</span>
@@ -82,11 +77,7 @@ export function OptionalClassFormField({
<FieldLabel htmlFor="shellClassName"> <FieldLabel htmlFor="shellClassName">
{t("mainConfig.shellClassName")} {t("common:optional")} {t("mainConfig.shellClassName")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input id="shellClassName" {...field} placeholder={t("common:placeholders.input")} />
id="shellClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -100,11 +91,7 @@ export function OptionalClassFormField({
<FieldLabel htmlFor="injectClassName"> <FieldLabel htmlFor="injectClassName">
{t("mainConfig.injectorClassName")} {t("common:optional")} {t("mainConfig.injectorClassName")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input id="injectClassName" {...field} placeholder={t("common:placeholders.input")} />
id="injectClassName"
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -1,14 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { ChevronDown, ChevronRight, InfoIcon } from "lucide-react"; import { ChevronDown, ChevronRight, InfoIcon } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Field, FieldLabel } from "@/components/ui/field"; import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
@@ -19,13 +18,9 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import { import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { env } from "@/config"; import { env } from "@/config";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field"; import { ShellTypeFormField } from "./shelltype-field";
@@ -60,43 +55,36 @@ export function CommandTabContent({
return ( return (
<TabsContent value="Command"> <TabsContent value="Command">
<Card> <Card>
<CardContent className="space-y-2 mt-4"> <CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} /> <ShellTypeFormField form={form} shellTypes={shellTypes} />
{server !== "Dubbo" && ( {server !== "Dubbo" && (
<Controller <Controller
control={form.control} control={form.control}
name="commandParamName" name="commandParamName"
render={({ field }) => ( render={({ field }) => (
<Field <Field className="gap-1" hidden={shellType.includes("WebSocket")}>
className="gap-1"
hidden={shellType.includes("WebSocket")}
>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FieldLabel> <FieldLabel>
{t("common:paramName")} {t("common:optional")} {t("common:paramName")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" /> <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>{t("common:paramName.description")}</p> <p>{t("common:paramName.description")}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
)} )}
<div <div
className="grid grid-cols-1 md:grid-cols-2 gap-2" className="grid grid-cols-1 gap-2 md:grid-cols-2"
hidden={ hidden={
shellType !== "BypassNginxWebSocket" && shellType !== "BypassNginxWebSocket" && shellType !== "BypassNginxJakartaWebSocket"
shellType !== "BypassNginxJakartaWebSocket"
} }
> >
<Controller <Controller
@@ -105,10 +93,7 @@ export function CommandTabContent({
render={({ field }) => ( render={({ field }) => (
<Field className="gap-1"> <Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel> <FieldLabel>{t("common:headerName")}</FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -120,16 +105,13 @@ export function CommandTabContent({
<FieldLabel> <FieldLabel>
{t("common:headerValue")} {t("common:optional")} {t("common:headerValue")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
</div> </div>
<Collapsible open={isAdvancedOpen} onOpenChange={setIsAdvancedOpen}> <Collapsible open={isAdvancedOpen} onOpenChange={setIsAdvancedOpen}>
<CollapsibleTrigger className="flex items-center gap-2 w-full py-2 text-sm font-medium hover:underline"> <CollapsibleTrigger className="flex w-full items-center gap-2 py-2 text-sm font-medium hover:underline">
{isAdvancedOpen ? ( {isAdvancedOpen ? (
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
) : ( ) : (
@@ -138,22 +120,16 @@ export function CommandTabContent({
{t("common:advancedConfig")} {t("common:advancedConfig")}
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="space-y-2 pt-2"> <CollapsibleContent className="space-y-2 pt-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller <Controller
control={form.control} control={form.control}
name="encryptor" name="encryptor"
render={({ field }) => ( render={({ field }) => (
<Field className="gap-1"> <Field className="gap-1">
<FieldLabel>{t("common:encryptor")}</FieldLabel> <FieldLabel>{t("common:encryptor")}</FieldLabel>
<Select <Select onValueChange={field.onChange} value={field.value} defaultValue="RAW">
onValueChange={field.onChange}
value={field.value}
defaultValue="RAW"
>
<SelectTrigger> <SelectTrigger>
<SelectValue <SelectValue data-placeholder={t("common:placeholders.select")} />
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{data?.encryptors?.map((v) => ( {data?.encryptors?.map((v) => (
@@ -178,9 +154,7 @@ export function CommandTabContent({
defaultValue="RuntimeExec" defaultValue="RuntimeExec"
> >
<SelectTrigger> <SelectTrigger>
<SelectValue <SelectValue data-placeholder={t("common:placeholders.select")} />
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{data?.implementationClasses?.map((v) => ( {data?.implementationClasses?.map((v) => (
@@ -202,11 +176,8 @@ export function CommandTabContent({
<FieldLabel> <FieldLabel>
{t("common:commandTemplate")} {t("common:optional")} {t("common:commandTemplate")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:commandTemplate.placeholder")} />
{...field} <p className="mt-1 text-xs text-muted-foreground">
placeholder={t("common:commandTemplate.placeholder")}
/>
<p className="text-xs text-muted-foreground mt-1">
{t("common:commandTemplate.description")} {t("common:commandTemplate.description")}
</p> </p>
</Field> </Field>
@@ -1,7 +1,10 @@
import type { MemShellFormSchema } from "@/types/schema";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldError, FieldLabel } from "@/components/ui/field"; import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@@ -10,7 +13,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { env } from "@/config"; import { env } from "@/config";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field"; import { ShellTypeFormField } from "./shelltype-field";
@@ -85,7 +88,7 @@ export default function CustomTabContent({
return ( return (
<TabsContent value="Custom"> <TabsContent value="Custom">
<Card> <Card>
<CardContent className="space-y-2 mt-4"> <CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} /> <ShellTypeFormField form={form} shellTypes={shellTypes} />
<Controller <Controller
control={form.control} control={form.control}
@@ -120,9 +123,7 @@ export default function CustomTabContent({
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (event) => { reader.onload = (event) => {
const base64String = const base64String =
(event.target?.result as string)?.split( (event.target?.result as string)?.split(",")[1] || "";
",",
)[1] || "";
field.onChange(base64String); field.onChange(base64String);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field"; import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field"; import { ShellTypeFormField } from "./shelltype-field";
@@ -19,9 +22,9 @@ export function GodzillaTabContent({
return ( return (
<TabsContent value="Godzilla"> <TabsContent value="Godzilla">
<Card> <Card>
<CardContent className="flex flex-col gap-2 mt-4"> <CardContent className="mt-4 flex flex-col gap-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} /> <ShellTypeFormField form={form} shellTypes={shellTypes} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller <Controller
control={form.control} control={form.control}
name="godzillaPass" name="godzillaPass"
@@ -30,10 +33,7 @@ export function GodzillaTabContent({
<FieldLabel> <FieldLabel>
{t("shellToolConfig.godzilla.pass")} {t("common:optional")} {t("shellToolConfig.godzilla.pass")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -45,10 +45,7 @@ export function GodzillaTabContent({
<FieldLabel> <FieldLabel>
{t("shellToolConfig.godzilla.key")} {t("common:optional")} {t("shellToolConfig.godzilla.key")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -58,10 +55,7 @@ export function GodzillaTabContent({
render={({ field }) => ( render={({ field }) => (
<Field className="gap-1"> <Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel> <FieldLabel>{t("common:headerName")}</FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -73,10 +67,7 @@ export function GodzillaTabContent({
<FieldLabel> <FieldLabel>
{t("common:headerValue")} {t("common:optional")} {t("common:headerValue")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field"; import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field"; import { ShellTypeFormField } from "./shelltype-field";
@@ -19,19 +22,16 @@ export function NeoRegTabContent({
return ( return (
<TabsContent value="NeoreGeorg"> <TabsContent value="NeoreGeorg">
<Card> <Card>
<CardContent className="space-y-2 mt-4"> <CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} /> <ShellTypeFormField form={form} shellTypes={shellTypes} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller <Controller
control={form.control} control={form.control}
name="headerName" name="headerName"
render={({ field }) => ( render={({ field }) => (
<Field className="gap-1"> <Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel> <FieldLabel>{t("common:headerName")}</FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -43,10 +43,7 @@ export function NeoRegTabContent({
<FieldLabel> <FieldLabel>
{t("common:headerValue")} {t("common:optional")} {t("common:headerValue")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
+9 -13
View File
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field"; import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field"; import { ShellTypeFormField } from "./shelltype-field";
@@ -23,13 +26,12 @@ export function ProxyTabContent({
return ( return (
<TabsContent value="Proxy"> <TabsContent value="Proxy">
<Card> <Card>
<CardContent className="space-y-2 mt-4"> <CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} /> <ShellTypeFormField form={form} shellTypes={shellTypes} />
<div <div
className="grid grid-cols-1 md:grid-cols-2 gap-2" className="grid grid-cols-1 gap-2 md:grid-cols-2"
hidden={ hidden={
shellType !== "BypassNginxWebSocket" && shellType !== "BypassNginxWebSocket" && shellType !== "BypassNginxJakartaWebSocket"
shellType !== "BypassNginxJakartaWebSocket"
} }
> >
<Controller <Controller
@@ -38,10 +40,7 @@ export function ProxyTabContent({
render={({ field }) => ( render={({ field }) => (
<Field className="gap-1"> <Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel> <FieldLabel>{t("common:headerName")}</FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -53,10 +52,7 @@ export function ProxyTabContent({
<FieldLabel> <FieldLabel>
{t("common:headerValue")} {t("common:optional")} {t("common:headerValue")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -1,5 +1,8 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form"; import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Field, FieldError, FieldLabel } from "@/components/ui/field"; import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
@@ -10,7 +13,6 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { cn, notNeedUrlPattern } from "@/lib/utils"; import { cn, notNeedUrlPattern } from "@/lib/utils";
import type { MemShellFormSchema } from "@/types/schema";
export function ShellTypeFormField({ export function ShellTypeFormField({
form, form,
@@ -23,7 +25,7 @@ export function ShellTypeFormField({
const shellType = useWatch({ control: form.control, name: "shellType" }); const shellType = useWatch({ control: form.control, name: "shellType" });
const needUrlPattern = !notNeedUrlPattern(shellType); const needUrlPattern = !notNeedUrlPattern(shellType);
return ( return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller <Controller
control={form.control} control={form.control}
name="shellType" name="shellType"
@@ -38,9 +40,7 @@ export function ShellTypeFormField({
value={field.value} value={field.value}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue <SelectValue data-placeholder={t("common:placeholders.select")} />
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent key={shellTypes?.join(",")}> <SelectContent key={shellTypes?.join(",")}>
{shellTypes?.length ? ( {shellTypes?.length ? (
@@ -50,9 +50,7 @@ export function ShellTypeFormField({
</SelectItem> </SelectItem>
)) ))
) : ( ) : (
<SelectItem value=" "> <SelectItem value=" ">{t("tips.shellToolNotSelected")}</SelectItem>
{t("tips.shellToolNotSelected")}
</SelectItem>
)} )}
</SelectContent> </SelectContent>
</Select> </Select>
+8 -11
View File
@@ -1,10 +1,13 @@
import type { MemShellFormSchema } from "@/types/schema";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { Field, FieldLabel } from "@/components/ui/field"; import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import type { MemShellFormSchema } from "@/types/schema";
import { OptionalClassFormField } from "./classname-field"; import { OptionalClassFormField } from "./classname-field";
import { ShellTypeFormField } from "./shelltype-field"; import { ShellTypeFormField } from "./shelltype-field";
@@ -21,19 +24,16 @@ export function Suo5TabContent({
return ( return (
<TabsContent value={tabValue}> <TabsContent value={tabValue}>
<Card> <Card>
<CardContent className="space-y-2 mt-4"> <CardContent className="mt-4 space-y-2">
<ShellTypeFormField form={form} shellTypes={shellTypes} /> <ShellTypeFormField form={form} shellTypes={shellTypes} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-2"> <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<Controller <Controller
control={form.control} control={form.control}
name="headerName" name="headerName"
render={({ field }) => ( render={({ field }) => (
<Field className="gap-1"> <Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel> <FieldLabel>{t("common:headerName")}</FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -45,10 +45,7 @@ export function Suo5TabContent({
<FieldLabel> <FieldLabel>
{t("common:headerValue")} {t("common:optional")} {t("common:headerValue")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:placeholders.input")} />
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
+11 -26
View File
@@ -1,19 +1,17 @@
import type { ProbeShellResult, ResponseBodyConfig } from "@/types/probeshell";
import { FileTextIcon } from "lucide-react"; import { FileTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { CopyableField } from "@/components/copyable-field"; import { CopyableField } from "@/components/copyable-field";
import { FeedbackAlert } from "@/components/memshell/results/feedback-alert"; import { FeedbackAlert } from "@/components/memshell/results/feedback-alert";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import type { ProbeShellResult, ResponseBodyConfig } from "@/types/probeshell";
export function BasicInfo({ export function BasicInfo({ generateResult }: Readonly<{ generateResult?: ProbeShellResult }>) {
generateResult,
}: Readonly<{ generateResult?: ProbeShellResult }>) {
const { t } = useTranslation(); const { t } = useTranslation();
const isBodyContent = const isBodyContent = generateResult?.probeConfig.probeMethod === "ResponseBody";
generateResult?.probeConfig.probeMethod === "ResponseBody";
const isFilterContent = generateResult?.probeConfig.probeContent === "Filter"; const isFilterContent = generateResult?.probeConfig.probeContent === "Filter";
const isBodyCommand = const isBodyCommand = isBodyContent && generateResult?.probeConfig.probeContent === "Command";
isBodyContent && generateResult?.probeConfig.probeContent === "Command";
return ( return (
<Card> <Card>
<CardHeader> <CardHeader>
@@ -30,29 +28,16 @@ export function BasicInfo({
{!isFilterContent && isBodyContent && ( {!isFilterContent && isBodyContent && (
<CopyableField <CopyableField
label={t("common:paramName")} label={t("common:paramName")}
value={ value={(generateResult?.probeContentConfig as ResponseBodyConfig).reqParamName}
(generateResult?.probeContentConfig as ResponseBodyConfig) text={(generateResult?.probeContentConfig as ResponseBodyConfig).reqParamName}
.reqParamName
}
text={
(generateResult?.probeContentConfig as ResponseBodyConfig)
.reqParamName
}
/> />
)} )}
{isBodyCommand && {isBodyCommand &&
(generateResult?.probeContentConfig as ResponseBodyConfig) (generateResult?.probeContentConfig as ResponseBodyConfig).commandTemplate && (
.commandTemplate && (
<CopyableField <CopyableField
label={t("common:commandTemplate")} label={t("common:commandTemplate")}
value={ value={(generateResult?.probeContentConfig as ResponseBodyConfig).commandTemplate}
(generateResult?.probeContentConfig as ResponseBodyConfig) text={(generateResult?.probeContentConfig as ResponseBodyConfig).commandTemplate}
.commandTemplate
}
text={
(generateResult?.probeContentConfig as ResponseBodyConfig)
.commandTemplate
}
/> />
)} )}
<CopyableField <CopyableField
@@ -1,14 +1,13 @@
import type { ServerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
import { InfoIcon, ServerIcon } from "lucide-react"; import { InfoIcon, ServerIcon } from "lucide-react";
import { useCallback, useEffect, useMemo } from "react"; import { useCallback, useEffect, useMemo } from "react";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { import { Field, FieldContent, FieldError, FieldLabel } from "@/components/ui/field";
Field,
FieldContent,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
Select, Select,
@@ -19,18 +18,10 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { SwitchField } from "@/components/ui/switch-field"; import { SwitchField } from "@/components/ui/switch-field";
import { import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import type { ServerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
// Hoisted static JSX to avoid recreation on each render (rendering-hoist-jsx) // Hoisted static JSX to avoid recreation on each render (rendering-hoist-jsx)
const infoIcon = ( const infoIcon = <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />;
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
);
const PROBE_OPTIONS = [ const PROBE_OPTIONS = [
{ value: "Server" as const, label: "server" }, { value: "Server" as const, label: "server" },
@@ -85,14 +76,11 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
Sleep: ["Server"], Sleep: ["Server"],
} as const; } as const;
const allowedValues = const allowedValues = filterMap[watchedProbeMethod as keyof typeof filterMap];
filterMap[watchedProbeMethod as keyof typeof filterMap];
if (!allowedValues) return PROBE_OPTIONS; if (!allowedValues) return PROBE_OPTIONS;
return PROBE_OPTIONS.filter((opt) => return PROBE_OPTIONS.filter((opt) => allowedValues.includes(opt.value as never));
allowedValues.includes(opt.value as never),
);
}, [watchedProbeMethod]); }, [watchedProbeMethod]);
const resetFormValues = useCallback(() => { const resetFormValues = useCallback(() => {
@@ -116,9 +104,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
const isFilter = watchedProbeContent === "Filter"; const isFilter = watchedProbeContent === "Filter";
const needParam = const needParam =
!isFilter && !isFilter &&
(isCommandBody || (isCommandBody || watchedProbeContent === "Bytecode" || watchedProbeContent === "ScriptEngine");
watchedProbeContent === "Bytecode" ||
watchedProbeContent === "ScriptEngine");
const isSleepMethod = watchedProbeMethod === "Sleep"; const isSleepMethod = watchedProbeMethod === "Sleep";
const isServerContent = watchedProbeContent === "Server"; const isServerContent = watchedProbeContent === "Server";
@@ -140,9 +126,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
<Select onValueChange={field.onChange} defaultValue={field.value}> <Select onValueChange={field.onChange} defaultValue={field.value}>
<div> <div>
<SelectTrigger> <SelectTrigger>
<SelectValue <SelectValue data-placeholder={t("common:placeholders.select")} />
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
</div> </div>
<SelectContent> <SelectContent>
@@ -162,24 +146,12 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
control={form.control} control={form.control}
name="server" name="server"
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field <Field className="gap-1" orientation="vertical" data-invalid={fieldState.invalid}>
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldContent> <FieldContent>
<FieldLabel htmlFor="server">{t("server")}</FieldLabel> <FieldLabel htmlFor="server">{t("server")}</FieldLabel>
<Select <Select onValueChange={field.onChange} defaultValue={field.value}>
onValueChange={field.onChange} <SelectTrigger id="server" aria-invalid={fieldState.invalid}>
defaultValue={field.value} <SelectValue data-placeholder={t("placeholders.select")} />
>
<SelectTrigger
id="server"
aria-invalid={fieldState.invalid}
>
<SelectValue
data-placeholder={t("placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{Object.keys(servers ?? {}) {Object.keys(servers ?? {})
@@ -191,9 +163,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
{fieldState.error && ( {fieldState.error && <FieldError errors={[fieldState.error]} />}
<FieldError errors={[fieldState.error]} />
)}
</FieldContent> </FieldContent>
</Field> </Field>
)} )}
@@ -204,11 +174,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
control={form.control} control={form.control}
name="host" name="host"
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field <Field className="gap-1" orientation="vertical" data-invalid={fieldState.invalid}>
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldLabel>{t("probeshell:dnslog.host")}</FieldLabel> <FieldLabel>{t("probeshell:dnslog.host")}</FieldLabel>
<Input placeholder={t("placeholders.input")} {...field} /> <Input placeholder={t("placeholders.input")} {...field} />
{fieldState.error && <FieldError errors={[fieldState.error]} />} {fieldState.error && <FieldError errors={[fieldState.error]} />}
@@ -223,20 +189,10 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field orientation="vertical" data-invalid={fieldState.invalid}> <Field orientation="vertical" data-invalid={fieldState.invalid}>
<FieldContent> <FieldContent>
<FieldLabel htmlFor="probeContent"> <FieldLabel htmlFor="probeContent">{t("probeshell:probeContent")}</FieldLabel>
{t("probeshell:probeContent")} <Select onValueChange={field.onChange} value={field.value || ""}>
</FieldLabel> <SelectTrigger aria-invalid={fieldState.invalid} id="probeContent">
<Select <SelectValue data-placeholder={t("common:placeholders.select")} />
onValueChange={field.onChange}
value={field.value || ""}
>
<SelectTrigger
aria-invalid={fieldState.invalid}
id="probeContent"
>
<SelectValue
data-placeholder={t("common:placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{filteredOptions.map((opt) => ( {filteredOptions.map((opt) => (
@@ -246,15 +202,13 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
{fieldState.error && ( {fieldState.error && <FieldError errors={[fieldState.error]} />}
<FieldError errors={[fieldState.error]} />
)}
</FieldContent> </FieldContent>
</Field> </Field>
)} )}
/> />
)} )}
<div className="flex gap-4 mt-4 flex-col lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3"> <div className="mt-4 flex flex-col gap-4 lg:grid lg:grid-cols-2 2xl:grid 2xl:grid-cols-3">
<SwitchField <SwitchField
control={form.control} control={form.control}
name="debug" name="debug"
@@ -287,7 +241,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
/> />
</div> </div>
{isBodyMethod && needParam && ( {isBodyMethod && needParam && (
<div className="space-y-2 pt-4 border-t mt-4"> <div className="mt-4 space-y-2 border-t pt-4">
<Controller <Controller
control={form.control} control={form.control}
name="reqParamName" name="reqParamName"
@@ -305,9 +259,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
</Tooltip> </Tooltip>
</div> </div>
<Input placeholder={t("placeholders.input")} {...field} /> <Input placeholder={t("placeholders.input")} {...field} />
{fieldState.error && ( {fieldState.error && <FieldError errors={[fieldState.error]} />}
<FieldError errors={[fieldState.error]} />
)}
</Field> </Field>
)} )}
/> />
@@ -322,11 +274,8 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
<FieldLabel> <FieldLabel>
{t("common:commandTemplate")} {t("common:optional")} {t("common:commandTemplate")} {t("common:optional")}
</FieldLabel> </FieldLabel>
<Input <Input {...field} placeholder={t("common:commandTemplate.placeholder")} />
{...field} <p className="mt-1 text-xs text-muted-foreground">
placeholder={t("common:commandTemplate.placeholder")}
/>
<p className="text-xs text-muted-foreground mt-1">
{t("common:commandTemplate.description")} {t("common:commandTemplate.description")}
</p> </p>
</Field> </Field>
@@ -334,31 +283,17 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
/> />
)} )}
{isSleepMethod && isServerContent && ( {isSleepMethod && isServerContent && (
<div className="space-y-2 pt-4 border-t mt-4"> <div className="mt-4 space-y-2 border-t pt-4">
<Controller <Controller
control={form.control} control={form.control}
name="sleepServer" name="sleepServer"
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field <Field className="gap-1" orientation="vertical" data-invalid={fieldState.invalid}>
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldContent> <FieldContent>
<FieldLabel htmlFor="sleepServer"> <FieldLabel htmlFor="sleepServer">{t("probeshell:sleepServer")}</FieldLabel>
{t("probeshell:sleepServer")} <Select onValueChange={field.onChange} value={field.value || ""}>
</FieldLabel> <SelectTrigger aria-invalid={fieldState.invalid} id="sleepServer">
<Select <SelectValue data-placeholder={t("placeholders.select")} />
onValueChange={field.onChange}
value={field.value || ""}
>
<SelectTrigger
aria-invalid={fieldState.invalid}
id="sleepServer"
>
<SelectValue
data-placeholder={t("placeholders.select")}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{MIDDLEWARE_OPTIONS.map(({ value, label }) => ( {MIDDLEWARE_OPTIONS.map(({ value, label }) => (
@@ -368,9 +303,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
{fieldState.error && ( {fieldState.error && <FieldError errors={[fieldState.error]} />}
<FieldError errors={[fieldState.error]} />
)}
</FieldContent> </FieldContent>
</Field> </Field>
)} )}
@@ -379,11 +312,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
control={form.control} control={form.control}
name="seconds" name="seconds"
render={({ field, fieldState }) => ( render={({ field, fieldState }) => (
<Field <Field className="gap-1" orientation="vertical" data-invalid={fieldState.invalid}>
className="gap-1"
orientation="vertical"
data-invalid={fieldState.invalid}
>
<FieldLabel>{t("probeshell:sleepSeconds")}</FieldLabel> <FieldLabel>{t("probeshell:sleepSeconds")}</FieldLabel>
<Input <Input
type="number" type="number"
@@ -391,9 +320,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
{...field} {...field}
onChange={(event) => field.onChange(+event.target.value)} onChange={(event) => field.onChange(+event.target.value)}
/> />
{fieldState.error && ( {fieldState.error && <FieldError errors={[fieldState.error]} />}
<FieldError errors={[fieldState.error]} />
)}
</Field> </Field>
)} )}
/> />
@@ -408,11 +335,7 @@ export default function MainConfigCard({ form, servers }: MainConfigCardProps) {
<FieldLabel htmlFor="shellClassName"> <FieldLabel htmlFor="shellClassName">
{t("probeshell:shellClassName")} {t("optional")} {t("probeshell:shellClassName")} {t("optional")}
</FieldLabel> </FieldLabel>
<Input <Input id="shellClassName" {...field} placeholder={t("placeholders.input")} />
id="shellClassName"
{...field}
placeholder={t("placeholders.input")}
/>
</Field> </Field>
)} )}
/> />
@@ -1,12 +1,14 @@
import type { PackerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
import { PackageIcon } from "lucide-react"; import { PackageIcon } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Controller, type UseFormReturn } from "react-hook-form"; import { Controller, type UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { FieldLabel } from "@/components/ui/field"; import { FieldLabel } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import type { PackerConfig } from "@/types/memshell";
import type { ProbeShellFormSchema } from "@/types/schema";
type Option = { type Option = {
name: string; name: string;
@@ -41,10 +43,7 @@ export default function PackageConfigCard({
setOptions(mappedOptions); setOptions(mappedOptions);
const currentValue = form.getValues("packingMethod"); const currentValue = form.getValues("packingMethod");
if ( if (filteredOptions.length > 0 && (!currentValue || !filteredOptions.includes(currentValue))) {
filteredOptions.length > 0 &&
(!currentValue || !filteredOptions.includes(currentValue))
) {
form.setValue("packingMethod", filteredOptions[0]); form.setValue("packingMethod", filteredOptions[0]);
} }
}, [form, packerConfig]); }, [form, packerConfig]);
@@ -89,9 +88,7 @@ export default function PackageConfigCard({
) : ( ) : (
<div className="flex items-center justify-center p-4"> <div className="flex items-center justify-center p-4">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" /> <div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<span className="text-sm text-muted-foreground"> <span className="text-sm text-muted-foreground">{t("loading")}</span>
{t("loading")}
</span>
</div> </div>
)} )}
</CardContent> </CardContent>
@@ -1,5 +1,6 @@
import { ScrollTextIcon } from "lucide-react"; import { ScrollTextIcon } from "lucide-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export function QuickUsage() { export function QuickUsage() {
@@ -13,7 +14,7 @@ export function QuickUsage() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ol className="list-decimal list-inside space-y-4 text-sm"> <ol className="list-inside list-decimal space-y-4 text-sm">
<li>{t("probeshell:quickUsage.step1")}</li> <li>{t("probeshell:quickUsage.step1")}</li>
<li>{t("probeshell:quickUsage.step2")}</li> <li>{t("probeshell:quickUsage.step2")}</li>
<li>{t("probeshell:quickUsage.step3")}</li> <li>{t("probeshell:quickUsage.step3")}</li>
@@ -1,7 +1,10 @@
import type { ProbeShellResult } from "@/types/probeshell";
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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
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";
import { BasicInfo } from "./basic-info"; import { BasicInfo } from "./basic-info";
@@ -26,9 +29,7 @@ export default function ShellResult({
return ( return (
<Tabs defaultValue="packResult"> <Tabs defaultValue="packResult">
<TabsList className="grid w-full grid-cols-1"> <TabsList className="grid w-full grid-cols-1">
<TabsTrigger value="packResult"> <TabsTrigger value="packResult">{t("common:generateResult")}</TabsTrigger>
{t("common:generateResult")}
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="packResult" className="space-y-2"> <TabsContent value="packResult" className="space-y-2">
<BasicInfo generateResult={generateResult} /> <BasicInfo generateResult={generateResult} />
@@ -44,13 +45,11 @@ export default function ShellResult({
<CodeViewer <CodeViewer
code={packResult} code={packResult}
header={ header={
<div className="flex items-center justify-between text-xs gap-2"> <div className="flex items-center justify-between gap-2 text-xs">
<span> <span>
{t("common:packerMethod")}{packMethod} {t("common:packerMethod")}{packMethod}
</span> </span>
<span className="text-muted-foreground"> <span className="text-muted-foreground">({packResult?.length})</span>
({packResult?.length})
</span>
</div> </div>
} }
wrapLongLines={!showCode} wrapLongLines={!showCode}
+3 -8
View File
@@ -1,3 +1,5 @@
import { create } from "@orama/orama";
import { useDocsSearch } from "fumadocs-core/search/client";
import { import {
SearchDialog, SearchDialog,
SearchDialogClose, SearchDialogClose,
@@ -9,8 +11,6 @@ import {
SearchDialogOverlay, SearchDialogOverlay,
type SharedProps, type SharedProps,
} from "fumadocs-ui/components/dialog/search"; } from "fumadocs-ui/components/dialog/search";
import { useDocsSearch } from "fumadocs-core/search/client";
import { create } from "@orama/orama";
import { useI18n } from "fumadocs-ui/contexts/i18n"; import { useI18n } from "fumadocs-ui/contexts/i18n";
function initOrama() { function initOrama() {
@@ -29,12 +29,7 @@ export default function DefaultSearchDialog(props: SharedProps) {
}); });
return ( return (
<SearchDialog <SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>
search={search}
onSearchChange={setSearch}
isLoading={query.isLoading}
{...props}
>
<SearchDialogOverlay /> <SearchDialogOverlay />
<SearchDialogContent> <SearchDialogContent>
<SearchDialogHeader> <SearchDialogHeader>
+2 -4
View File
@@ -1,10 +1,8 @@
export function TailwindIndicator() { export function TailwindIndicator() {
return ( return (
<div className="fixed bottom-1 right-1 z-50 flex size-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white"> <div className="fixed right-1 bottom-1 z-50 flex size-6 items-center justify-center rounded-full bg-gray-800 p-3 font-mono text-xs text-white">
<div className="block sm:hidden">xs</div> <div className="block sm:hidden">xs</div>
<div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden"> <div className="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden">sm</div>
sm
</div>
<div className="hidden md:block lg:hidden xl:hidden 2xl:hidden">md</div> <div className="hidden md:block lg:hidden xl:hidden 2xl:hidden">md</div>
<div className="hidden lg:block xl:hidden 2xl:hidden">lg</div> <div className="hidden lg:block xl:hidden 2xl:hidden">lg</div>
<div className="hidden xl:block 2xl:hidden">xl</div> <div className="hidden xl:block 2xl:hidden">xl</div>
+15 -38
View File
@@ -1,5 +1,7 @@
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import type * as React from "react"; import type * as React from "react";
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -8,26 +10,19 @@ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
} }
function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) { function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
return ( return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />;
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
);
} }
function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) { function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
return ( return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />;
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
);
} }
function AlertDialogOverlay({ function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) {
className,
...props
}: AlertDialogPrimitive.Backdrop.Props) {
return ( return (
<AlertDialogPrimitive.Backdrop <AlertDialogPrimitive.Backdrop
data-slot="alert-dialog-overlay" data-slot="alert-dialog-overlay"
className={cn( className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50", "fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-open:animate-in data-open:fade-in-0 supports-backdrop-filter:backdrop-blur-xs",
className, className,
)} )}
{...props} {...props}
@@ -49,7 +44,7 @@ function AlertDialogContent({
data-slot="alert-dialog-content" data-slot="alert-dialog-content"
data-size={size} data-size={size}
className={cn( className={cn(
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none", "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl bg-background p-6 ring-1 ring-foreground/10 duration-100 outline-none data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg",
className, className,
)} )}
{...props} {...props}
@@ -58,10 +53,7 @@ function AlertDialogContent({
); );
} }
function AlertDialogHeader({ function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) {
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="alert-dialog-header" data-slot="alert-dialog-header"
@@ -74,10 +66,7 @@ function AlertDialogHeader({
); );
} }
function AlertDialogFooter({ function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) {
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="alert-dialog-footer" data-slot="alert-dialog-footer"
@@ -90,15 +79,12 @@ function AlertDialogFooter({
); );
} }
function AlertDialogMedia({ function AlertDialogMedia({ className, ...props }: React.ComponentProps<"div">) {
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="alert-dialog-media" data-slot="alert-dialog-media"
className={cn( className={cn(
"bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-md sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8", "mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
className, className,
)} )}
{...props} {...props}
@@ -130,7 +116,7 @@ function AlertDialogDescription({
<AlertDialogPrimitive.Description <AlertDialogPrimitive.Description
data-slot="alert-dialog-description" data-slot="alert-dialog-description"
className={cn( className={cn(
"text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", "text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className, className,
)} )}
{...props} {...props}
@@ -138,17 +124,8 @@ function AlertDialogDescription({
); );
} }
function AlertDialogAction({ function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof Button>) {
className, return <Button data-slot="alert-dialog-action" className={cn(className)} {...props} />;
...props
}: React.ComponentProps<typeof Button>) {
return (
<Button
data-slot="alert-dialog-action"
className={cn(className)}
{...props}
/>
);
} }
function AlertDialogCancel({ function AlertDialogCancel({
+5 -7
View File
@@ -1,6 +1,7 @@
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react"; import type * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const alertVariants = cva( const alertVariants = cva(
@@ -39,7 +40,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="alert-title" data-slot="alert-title"
className={cn( className={cn(
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3", "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
className, className,
)} )}
{...props} {...props}
@@ -47,15 +48,12 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
); );
} }
function AlertDescription({ function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="alert-description" data-slot="alert-description"
className={cn( className={cn(
"text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3", "text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
className, className,
)} )}
{...props} {...props}
+11 -26
View File
@@ -1,6 +1,7 @@
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import type * as React from "react"; import type * as React from "react";
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Avatar({ function Avatar({
@@ -15,7 +16,7 @@ function Avatar({
data-slot="avatar" data-slot="avatar"
data-size={size} data-size={size}
className={cn( className={cn(
"size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten", "group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
className, className,
)} )}
{...props} {...props}
@@ -27,24 +28,18 @@ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
return ( return (
<AvatarPrimitive.Image <AvatarPrimitive.Image
data-slot="avatar-image" data-slot="avatar-image"
className={cn( className={cn("aspect-square size-full rounded-full object-cover", className)}
"rounded-full aspect-square size-full object-cover",
className,
)}
{...props} {...props}
/> />
); );
} }
function AvatarFallback({ function AvatarFallback({ className, ...props }: AvatarPrimitive.Fallback.Props) {
className,
...props
}: AvatarPrimitive.Fallback.Props) {
return ( return (
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
data-slot="avatar-fallback" data-slot="avatar-fallback"
className={cn( className={cn(
"bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs", "flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
className, className,
)} )}
{...props} {...props}
@@ -57,7 +52,7 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
<span <span
data-slot="avatar-badge" data-slot="avatar-badge"
className={cn( className={cn(
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none", "absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden", "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
@@ -73,7 +68,7 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="avatar-group" data-slot="avatar-group"
className={cn( className={cn(
"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2", "group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
className, className,
)} )}
{...props} {...props}
@@ -81,15 +76,12 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
); );
} }
function AvatarGroupCount({ function AvatarGroupCount({ className, ...props }: React.ComponentProps<"div">) {
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="avatar-group-count" data-slot="avatar-group-count"
className={cn( className={cn(
"bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2", "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className, className,
)} )}
{...props} {...props}
@@ -97,11 +89,4 @@ function AvatarGroupCount({
); );
} }
export { export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarBadge };
Avatar,
AvatarImage,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarBadge,
};
+3 -6
View File
@@ -10,14 +10,11 @@ const badgeVariants = cva(
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
secondary: secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive: destructive:
"bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20", "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
outline: outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
}, },
+6 -17
View File
@@ -12,7 +12,7 @@ function Card({
data-slot="card" data-slot="card"
data-size={size} data-size={size}
className={cn( className={cn(
"ring-foreground/10 bg-card text-card-foreground gap-2 overflow-hidden rounded-xl pb-6 text-sm shadow-xs ring-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", "group/card flex flex-col gap-2 overflow-hidden rounded-xl bg-card pb-6 text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className, className,
)} )}
{...props} {...props}
@@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card-header" data-slot="card-header"
className={cn( className={cn(
"gap-1 rounded-t-xl px-6 pt-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]", "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-6 pt-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4",
className, className,
)} )}
{...props} {...props}
@@ -50,7 +50,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-description" data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
); );
@@ -60,10 +60,7 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-action" data-slot="card-action"
className={cn( className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className,
)}
{...props} {...props}
/> />
); );
@@ -84,7 +81,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card-footer" data-slot="card-footer"
className={cn( className={cn(
"rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4 flex items-center", "flex items-center rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4",
className, className,
)} )}
{...props} {...props}
@@ -92,12 +89,4 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
); );
} }
export { export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};
+2 -6
View File
@@ -5,15 +5,11 @@ function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
} }
function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) { function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
return ( return <CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />;
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
);
} }
function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) { function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
return ( return <CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />;
<CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
);
} }
export { Collapsible, CollapsibleTrigger, CollapsibleContent }; export { Collapsible, CollapsibleTrigger, CollapsibleContent };
+27 -37
View File
@@ -1,5 +1,6 @@
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority";
import { useMemo } from "react"; import { useMemo } from "react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -51,29 +52,26 @@ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
); );
} }
const fieldVariants = cva( const fieldVariants = cva("group/field flex w-full gap-3 data-[invalid=true]:text-destructive", {
"group/field flex w-full gap-3 data-[invalid=true]:text-destructive", variants: {
{ orientation: {
variants: { vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
orientation: { horizontal: [
vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], "flex-row items-center",
horizontal: [ "[&>[data-slot=field-label]]:flex-auto",
"flex-row items-center", "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
"[&>[data-slot=field-label]]:flex-auto", ],
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", responsive: [
], "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto",
responsive: [ "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
"flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto", "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
"@md/field-group:[&>[data-slot=field-label]]:flex-auto", ],
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
},
},
defaultVariants: {
orientation: "vertical",
}, },
}, },
); defaultVariants: {
orientation: "vertical",
},
});
function Field({ function Field({
className, className,
@@ -94,26 +92,20 @@ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="field-content" data-slot="field-content"
className={cn( className={cn("group/field-content flex flex-1 flex-col gap-1.5 leading-snug", className)}
"group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
className,
)}
{...props} {...props}
/> />
); );
} }
function FieldLabel({ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
className,
...props
}: React.ComponentProps<typeof Label>) {
return ( return (
<Label <Label
data-slot="field-label" data-slot="field-label"
className={cn( className={cn(
"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50", "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4", "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
"has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10", "has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
className, className,
)} )}
{...props} {...props}
@@ -139,9 +131,9 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
<p <p
data-slot="field-description" data-slot="field-description"
className={cn( className={cn(
"text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance", "text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5", "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
"[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className, className,
)} )}
{...props} {...props}
@@ -169,7 +161,7 @@ function FieldSeparator({
<Separator className="absolute inset-0 top-1/2" /> <Separator className="absolute inset-0 top-1/2" />
{children && ( {children && (
<span <span
className="bg-background text-muted-foreground relative mx-auto block w-fit px-2" className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
data-slot="field-separator-content" data-slot="field-separator-content"
> >
{children} {children}
@@ -196,9 +188,7 @@ function FieldError({
return null; return null;
} }
const uniqueErrors = [ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
...new Map(errors.map((error) => [error?.message, error])).values(),
];
if (uniqueErrors?.length === 1) { if (uniqueErrors?.length === 1) {
return uniqueErrors[0]?.message; return uniqueErrors[0]?.message;
@@ -223,7 +213,7 @@ function FieldError({
<div <div
role="alert" role="alert"
data-slot="field-error" data-slot="field-error"
className={cn("text-destructive text-sm font-normal", className)} className={cn("text-sm font-normal text-destructive", className)}
{...props} {...props}
> >
{content} {content}
+3 -2
View File
@@ -1,6 +1,7 @@
import { Input as InputPrimitive } from "@base-ui/react/input";
import type * as React from "react"; import type * as React from "react";
import { Input as InputPrimitive } from "@base-ui/react/input";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) { function Input({ className, type, ...props }: React.ComponentProps<"input">) {
@@ -9,7 +10,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type} type={type}
data-slot="input" data-slot="input"
className={cn( className={cn(
"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 rounded-md border bg-transparent px-2.5 py-1 text-sm shadow-xs transition-[color,box-shadow] file:h-6 file:text-sm file:font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", "h-8 w-full min-w-0 rounded-md border border-input bg-transparent px-2.5 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className, className,
)} )}
{...props} {...props}
+1 -1
View File
@@ -8,7 +8,7 @@ function Label({ className, ...props }: React.ComponentProps<"label">) {
<label <label
data-slot="label" data-slot="label"
className={cn( className={cn(
"gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed", "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className, className,
)} )}
{...props} {...props}
+4 -3
View File
@@ -1,13 +1,14 @@
import { Radio as RadioPrimitive } from "@base-ui/react/radio"; import { Radio as RadioPrimitive } from "@base-ui/react/radio";
import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group"; import { RadioGroup as RadioGroupPrimitive } from "@base-ui/react/radio-group";
import { CircleIcon } from "lucide-react"; import { CircleIcon } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) { function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
return ( return (
<RadioGroupPrimitive <RadioGroupPrimitive
data-slot="radio-group" data-slot="radio-group"
className={cn("grid gap-2 w-full", className)} className={cn("grid w-full gap-2", className)}
{...props} {...props}
/> />
); );
@@ -18,14 +19,14 @@ function RadioGroupItem({ className, ...props }: RadioPrimitive.Root.Props) {
<RadioPrimitive.Root <RadioPrimitive.Root
data-slot="radio-group-item" data-slot="radio-group-item"
className={cn( className={cn(
"border-input text-primary dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 flex size-4 rounded-full shadow-xs focus-visible:ring-[3px] aria-invalid:ring-[3px] group/radio-group-item peer relative aspect-square shrink-0 border outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50", "group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className, className,
)} )}
{...props} {...props}
> >
<RadioPrimitive.Indicator <RadioPrimitive.Indicator
data-slot="radio-group-indicator" data-slot="radio-group-indicator"
className="group-aria-invalid/radio-group-item:text-destructive text-primary flex size-4 items-center justify-center" className="flex size-4 items-center justify-center text-primary group-aria-invalid/radio-group-item:text-destructive"
> >
<CircleIcon className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-current" /> <CircleIcon className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-current" />
</RadioPrimitive.Indicator> </RadioPrimitive.Indicator>
+15 -25
View File
@@ -1,6 +1,8 @@
import type * as React from "react";
import { Select as SelectPrimitive } from "@base-ui/react/select"; import { Select as SelectPrimitive } from "@base-ui/react/select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import type * as React from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
const Select = SelectPrimitive.Root; const Select = SelectPrimitive.Root;
@@ -38,16 +40,14 @@ function SelectTrigger({
data-slot="select-trigger" data-slot="select-trigger"
data-size={size} data-size={size}
className={cn( className={cn(
"border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-full items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0", "flex w-full items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className, className,
)} )}
{...props} {...props}
> >
{children} {children}
<SelectPrimitive.Icon <SelectPrimitive.Icon
render={ render={<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />}
<ChevronDownIcon className="text-muted-foreground size-4 pointer-events-none" />
}
/> />
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
); );
@@ -80,7 +80,7 @@ function SelectContent({
<SelectPrimitive.Popup <SelectPrimitive.Popup
data-slot="select-content" data-slot="select-content"
className={cn( className={cn(
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-md shadow-md ring-1 duration-100 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto", "relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className, className,
)} )}
{...props} {...props}
@@ -94,34 +94,27 @@ function SelectContent({
); );
} }
function SelectLabel({ function SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) {
className,
...props
}: SelectPrimitive.GroupLabel.Props) {
return ( return (
<SelectPrimitive.GroupLabel <SelectPrimitive.GroupLabel
data-slot="select-label" data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
{...props} {...props}
/> />
); );
} }
function SelectItem({ function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) {
className,
children,
...props
}: SelectPrimitive.Item.Props) {
return ( return (
<SelectPrimitive.Item <SelectPrimitive.Item
data-slot="select-item" data-slot="select-item"
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", "relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className, className,
)} )}
{...props} {...props}
> >
<SelectPrimitive.ItemText className="flex flex-1 gap-2 shrink-0 whitespace-nowrap"> <SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
{children} {children}
</SelectPrimitive.ItemText> </SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator <SelectPrimitive.ItemIndicator
@@ -135,14 +128,11 @@ function SelectItem({
); );
} }
function SelectSeparator({ function SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) {
className,
...props
}: SelectPrimitive.Separator.Props) {
return ( return (
<SelectPrimitive.Separator <SelectPrimitive.Separator
data-slot="select-separator" data-slot="select-separator"
className={cn("bg-border -mx-1 my-1 h-px pointer-events-none", className)} className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
{...props} {...props}
/> />
); );
@@ -156,7 +146,7 @@ function SelectScrollUpButton({
<SelectPrimitive.ScrollUpArrow <SelectPrimitive.ScrollUpArrow
data-slot="select-scroll-up-button" data-slot="select-scroll-up-button"
className={cn( className={cn(
"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 top-0 w-full", "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className, className,
)} )}
{...props} {...props}
@@ -174,7 +164,7 @@ function SelectScrollDownButton({
<SelectPrimitive.ScrollDownArrow <SelectPrimitive.ScrollDownArrow
data-slot="select-scroll-down-button" data-slot="select-scroll-down-button"
className={cn( className={cn(
"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 bottom-0 w-full", "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className, className,
)} )}
{...props} {...props}
+2 -6
View File
@@ -2,17 +2,13 @@ import { Separator as SeparatorPrimitive } from "@base-ui/react/separator";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Separator({ function Separator({ className, orientation = "horizontal", ...props }: SeparatorPrimitive.Props) {
className,
orientation = "horizontal",
...props
}: SeparatorPrimitive.Props) {
return ( return (
<SeparatorPrimitive <SeparatorPrimitive
data-slot="separator" data-slot="separator"
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch", "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:self-stretch",
className, className,
)} )}
{...props} {...props}
+1 -1
View File
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="skeleton" data-slot="skeleton"
className={cn("bg-muted rounded-md animate-pulse", className)} className={cn("animate-pulse rounded-md bg-muted", className)}
{...props} {...props}
/> />
); );
+1
View File
@@ -1,4 +1,5 @@
import { Loader2Icon } from "lucide-react"; import { Loader2Icon } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Spinner({ className, ...props }: React.ComponentProps<"svg">) { function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
+5 -19
View File
@@ -1,23 +1,13 @@
import { InfoIcon } from "lucide-react"; import { InfoIcon } from "lucide-react";
import { memo } from "react"; import { memo } from "react";
import { import { type Control, Controller, type FieldValues, type Path } from "react-hook-form";
type Control,
Controller,
type FieldValues,
type Path,
} from "react-hook-form";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
// Hoisted static JSX to avoid recreation on each render // Hoisted static JSX to avoid recreation on each render
const infoIcon = ( const infoIcon = <InfoIcon className="h-3.5 w-3.5 cursor-help text-muted-foreground" />;
<InfoIcon className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
);
interface SwitchFieldProps<T extends FieldValues> { interface SwitchFieldProps<T extends FieldValues> {
readonly name: Path<T>; readonly name: Path<T>;
@@ -38,11 +28,7 @@ function SwitchFieldInner<T extends FieldValues>({
name={name} name={name}
render={({ field }) => ( render={({ field }) => (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Switch <Switch id={name} checked={field.value} onCheckedChange={field.onChange} />
id={name}
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label htmlFor={name}>{label}</Label> <Label htmlFor={name}>{label}</Label>
<Tooltip> <Tooltip>
<TooltipTrigger>{infoIcon}</TooltipTrigger> <TooltipTrigger>{infoIcon}</TooltipTrigger>
+2 -2
View File
@@ -14,14 +14,14 @@ function Switch({
data-slot="switch" data-slot="switch"
data-size={size} data-size={size}
className={cn( className={cn(
"data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent shadow-xs focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-[18.4px] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 peer group/switch relative inline-flex items-center transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 data-disabled:cursor-not-allowed data-disabled:opacity-50", "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 data-checked:bg-primary data-disabled:cursor-not-allowed data-disabled:opacity-50 data-unchecked:bg-input data-[size=default]:h-[18.4px] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:data-unchecked:bg-input/80",
className, className,
)} )}
{...props} {...props}
> >
<SwitchPrimitive.Thumb <SwitchPrimitive.Thumb
data-slot="switch-thumb" data-slot="switch-thumb"
className="bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform" className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-checked:bg-primary-foreground dark:data-unchecked:bg-foreground"
/> />
</SwitchPrimitive.Root> </SwitchPrimitive.Root>
); );
+6 -13
View File
@@ -3,19 +3,12 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function Tabs({ function Tabs({ className, orientation = "horizontal", ...props }: TabsPrimitive.Root.Props) {
className,
orientation = "horizontal",
...props
}: TabsPrimitive.Root.Props) {
return ( return (
<TabsPrimitive.Root <TabsPrimitive.Root
data-slot="tabs" data-slot="tabs"
data-orientation={orientation} data-orientation={orientation}
className={cn( className={cn("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className)}
"gap-2 group/tabs flex data-[orientation=horizontal]:flex-col",
className,
)}
{...props} {...props}
/> />
); );
@@ -56,10 +49,10 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
<TabsPrimitive.Tab <TabsPrimitive.Tab
data-slot="tabs-trigger" data-slot="tabs-trigger"
className={cn( className={cn(
"gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", "relative 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 text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground", "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
className, className,
)} )}
{...props} {...props}
@@ -71,7 +64,7 @@ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
return ( return (
<TabsPrimitive.Panel <TabsPrimitive.Panel
data-slot="tabs-content" data-slot="tabs-content"
className={cn("text-sm flex-1 outline-none", className)} className={cn("flex-1 text-sm outline-none", className)}
{...props} {...props}
/> />
); );
+1 -1
View File
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
<textarea <textarea
data-slot="textarea" data-slot="textarea"
className={cn( className={cn(
"border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50", "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
className, className,
)} )}
{...props} {...props}
+5 -17
View File
@@ -2,17 +2,8 @@ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function TooltipProvider({ function TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) {
delay = 0, return <TooltipPrimitive.Provider data-slot="tooltip-provider" delay={delay} {...props} />;
...props
}: TooltipPrimitive.Provider.Props) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delay={delay}
{...props}
/>
);
} }
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) { function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
@@ -36,10 +27,7 @@ function TooltipContent({
children, children,
...props ...props
}: TooltipPrimitive.Popup.Props & }: TooltipPrimitive.Popup.Props &
Pick< Pick<TooltipPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) {
TooltipPrimitive.Positioner.Props,
"align" | "alignOffset" | "side" | "sideOffset"
>) {
return ( return (
<TooltipPrimitive.Portal> <TooltipPrimitive.Portal>
<TooltipPrimitive.Positioner <TooltipPrimitive.Positioner
@@ -52,13 +40,13 @@ function TooltipContent({
<TooltipPrimitive.Popup <TooltipPrimitive.Popup
data-slot="tooltip-content" data-slot="tooltip-content"
className={cn( className={cn(
"animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 rounded-md px-3 py-1.5 text-xs bg-foreground text-background z-50 max-w-xs origin-(--transform-origin) wrap-break-word", "z-50 max-w-xs origin-(--transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs wrap-break-word text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
className, className,
)} )}
{...props} {...props}
> >
{children} {children}
<TooltipPrimitive.Arrow className="size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground z-50 data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" /> <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
</TooltipPrimitive.Popup> </TooltipPrimitive.Popup>
</TooltipPrimitive.Positioner> </TooltipPrimitive.Positioner>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
+1 -3
View File
@@ -47,9 +47,7 @@ function safeParseYup<T>(schema: yup.ObjectSchema<any>, data: unknown) {
} }
const createEnv = () => { const createEnv = () => {
const envVars = Object.entries(import.meta.env).reduce< const envVars = Object.entries(import.meta.env).reduce<Record<string, string>>((acc, curr) => {
Record<string, string>
>((acc, curr) => {
const [key, value] = curr; const [key, value] = curr;
if (typeof value === "string") { if (typeof value === "string") {
if (key.startsWith("VITE_APP_")) { if (key.startsWith("VITE_APP_")) {
+8 -6
View File
@@ -1,4 +1,7 @@
import type { Route } from "./+types/page"; import type { Route } from "./+types/page";
import browserCollections from "collections/browser";
import { useFumadocsLoader } from "fumadocs-core/source/client";
import { DocsLayout } from "fumadocs-ui/layouts/docs"; import { DocsLayout } from "fumadocs-ui/layouts/docs";
import { import {
DocsBody, DocsBody,
@@ -8,14 +11,13 @@ import {
MarkdownCopyButton, MarkdownCopyButton,
ViewOptionsPopover, ViewOptionsPopover,
} from "fumadocs-ui/layouts/docs/page"; } from "fumadocs-ui/layouts/docs/page";
import { getPageMarkdownUrl, source } from "@/lib/source";
import browserCollections from "collections/browser";
import { baseOptions } from "@/lib/layout.shared";
import { useFumadocsLoader } from "fumadocs-core/source/client";
import { useMDXComponents } from "@/components/mdx"; import { useMDXComponents } from "@/components/mdx";
import { baseOptions } from "@/lib/layout.shared";
import { getPageMarkdownUrl, source } from "@/lib/source";
export async function loader({ params }: Route.LoaderArgs) { export async function loader({ params }: Route.LoaderArgs) {
const slugs = params["*"].split("/").filter((v) => v.length > 0); const slugs = params["*"].split("/").filter((v: string | any[]) => v.length > 0);
const page = source.getPage(slugs); const page = source.getPage(slugs);
if (!page) throw new Response("Not found", { status: 404 }); if (!page) throw new Response("Not found", { status: 404 });
@@ -44,7 +46,7 @@ const clientLoader = browserCollections.docs.createClientLoader({
<meta name="description" content={frontmatter.description} /> <meta name="description" content={frontmatter.description} />
<DocsTitle>{frontmatter.title}</DocsTitle> <DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription> <DocsDescription>{frontmatter.description}</DocsDescription>
<div className="flex flex-row gap-2 items-center border-b -mt-4 pb-6"> <div className="-mt-4 flex flex-row items-center gap-2 border-b pb-6">
<MarkdownCopyButton markdownUrl={markdownUrl} /> <MarkdownCopyButton markdownUrl={markdownUrl} />
<ViewOptionsPopover <ViewOptionsPopover
markdownUrl={markdownUrl} markdownUrl={markdownUrl}
+1
View File
@@ -1,6 +1,7 @@
import { stopwords as mandarinStopwords } from "@orama/stopwords/mandarin"; import { stopwords as mandarinStopwords } from "@orama/stopwords/mandarin";
import { createTokenizer } from "@orama/tokenizers/mandarin"; import { createTokenizer } from "@orama/tokenizers/mandarin";
import { createFromSource } from "fumadocs-core/search/server"; import { createFromSource } from "fumadocs-core/search/server";
import { source } from "@/lib/source"; import { source } from "@/lib/source";
const server = createFromSource(source, { const server = createFromSource(source, {
+2 -7
View File
@@ -1,5 +1,6 @@
import i18n from "i18next"; import i18n from "i18next";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import commonEN from "@/i18n/common/en.json"; import commonEN from "@/i18n/common/en.json";
import commonZH from "@/i18n/common/zh-CN.json"; import commonZH from "@/i18n/common/zh-CN.json";
import memshellEN from "@/i18n/memshell/en.json"; import memshellEN from "@/i18n/memshell/en.json";
@@ -20,13 +21,7 @@ const getStoredLanguage = () => {
}; };
const fallbackLng = "zh-CN"; const fallbackLng = "zh-CN";
export const ns = [ export const ns = ["default", "common", "memshell", "probeshell", "errors"] as const;
"default",
"common",
"memshell",
"probeshell",
"errors",
] as const;
export const defaultNS = "default" as const; export const defaultNS = "default" as const;
const resources = { const resources = {
+1
View File
@@ -1,4 +1,5 @@
import type { LinkItemType } from "fumadocs-ui/layouts/shared"; import type { LinkItemType } from "fumadocs-ui/layouts/shared";
import { LanguageSwitcher } from "@/components/language-switcher"; import { LanguageSwitcher } from "@/components/language-switcher";
export const siteConfig = { export const siteConfig = {
+1 -1
View File
@@ -1,5 +1,5 @@
import { loader, type InferPageType } from "fumadocs-core/source";
import { docs } from "collections/server"; import { docs } from "collections/server";
import { loader, type InferPageType } from "fumadocs-core/source";
import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons"; import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons";
export const source = loader({ export const source = loader({
+2 -10
View File
@@ -5,11 +5,7 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
export function downloadContent( export function downloadContent(content: Blob, fileName: string, fileExtension: string) {
content: Blob,
fileName: string,
fileExtension: string,
) {
const link = document.createElement("a"); const link = document.createElement("a");
link.href = URL.createObjectURL(content); link.href = URL.createObjectURL(content);
link.download = `${fileName}${fileExtension}`; link.download = `${fileName}${fileExtension}`;
@@ -27,11 +23,7 @@ export function base64ToBytes(base64String: string) {
return new Uint8Array(byteNumbers); return new Uint8Array(byteNumbers);
} }
export function downloadBytes( export function downloadBytes(base64String: string, className?: string, jarName?: string) {
base64String: string,
className?: string,
jarName?: string,
) {
const byteArray = base64ToBytes(base64String); const byteArray = base64ToBytes(base64String);
const blob = new Blob([byteArray], { const blob = new Blob([byteArray], {
type: className ? "application/java-vm" : "application/java-archive", type: className ? "application/java-vm" : "application/java-archive",
+2 -1
View File
@@ -1,6 +1,7 @@
import { source } from "@/lib/source";
import { llms } from "fumadocs-core/source"; import { llms } from "fumadocs-core/source";
import { source } from "@/lib/source";
export function loader() { export function loader() {
return new Response(llms(source).index()); return new Response(llms(source).index());
} }
+1
View File
@@ -1,4 +1,5 @@
import type { Route } from "./+types/mdx"; import type { Route } from "./+types/mdx";
import { getLLMText, source } from "@/lib/source"; import { getLLMText, source } from "@/lib/source";
export async function loader({ params }: Route.LoaderArgs) { export async function loader({ params }: Route.LoaderArgs) {
+3 -4
View File
@@ -1,6 +1,7 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
@@ -11,7 +12,5 @@ const queryClient = new QueryClient({
}); });
export function QueryProvider({ children }: Readonly<{ children: ReactNode }>) { export function QueryProvider({ children }: Readonly<{ children: ReactNode }>) {
return ( return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
} }
+11 -9
View File
@@ -1,4 +1,9 @@
import type { Route } from "./+types/root";
import { RootProvider } from "fumadocs-ui/provider/react-router"; import { RootProvider } from "fumadocs-ui/provider/react-router";
import { I18nextProvider } from "react-i18next";
import "./app.css";
import { import {
isRouteErrorResponse, isRouteErrorResponse,
Links, Links,
@@ -7,13 +12,12 @@ import {
Scripts, Scripts,
ScrollRestoration, ScrollRestoration,
} from "react-router"; } from "react-router";
import type { Route } from "./+types/root";
import "./app.css";
import { I18nextProvider } from "react-i18next";
import SearchDialog from "@/components/search"; import SearchDialog from "@/components/search";
import { TailwindIndicator } from "@/components/tailwind-indicator"; import { TailwindIndicator } from "@/components/tailwind-indicator";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import { env } from "@/config"; import { env } from "@/config";
import i18n from "./i18n/i18n"; import i18n from "./i18n/i18n";
import { QueryProvider } from "./providers/query-client-provider"; import { QueryProvider } from "./providers/query-client-provider";
@@ -39,7 +43,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
<Meta /> <Meta />
<Links /> <Links />
</head> </head>
<body className="flex flex-col min-h-screen"> <body className="flex min-h-screen flex-col">
<RootProvider search={{ SearchDialog }}> <RootProvider search={{ SearchDialog }}>
<Toaster /> <Toaster />
<QueryProvider> <QueryProvider>
@@ -66,20 +70,18 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
if (isRouteErrorResponse(error)) { if (isRouteErrorResponse(error)) {
message = error.status === 404 ? "404" : "Error"; message = error.status === 404 ? "404" : "Error";
details = details =
error.status === 404 error.status === 404 ? "The requested page could not be found." : error.statusText || details;
? "The requested page could not be found."
: error.statusText || details;
} else if (import.meta.env.DEV && error && error instanceof Error) { } else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message; details = error.message;
stack = error.stack; stack = error.stack;
} }
return ( return (
<main className="pt-16 p-4 container mx-auto"> <main className="container mx-auto p-4 pt-16">
<h1>{message}</h1> <h1>{message}</h1>
<p>{details}</p> <p>{details}</p>
{stack && ( {stack && (
<pre className="w-full p-4 overflow-x-auto"> <pre className="w-full overflow-x-auto p-4">
<code>{stack}</code> <code>{stack}</code>
</pre> </pre>
)} )}
+72 -100
View File
@@ -15,6 +15,8 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { Link } from "react-router"; import { Link } from "react-router";
import { Icons } from "@/components/icons";
import { LineShadowText } from "@/components/magicui/line-shadow-text"; import { LineShadowText } from "@/components/magicui/line-shadow-text";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -23,8 +25,8 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter } from "@/components/ui/card"; import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { env } from "@/config"; import { env } from "@/config";
import { siteConfig } from "@/lib/config"; import { siteConfig } from "@/lib/config";
import { baseOptions } from "../lib/layout.shared"; import { baseOptions } from "../lib/layout.shared";
import { Icons } from "@/components/icons";
type VersionInfo = { type VersionInfo = {
currentVersion: string; currentVersion: string;
@@ -76,32 +78,30 @@ export default function AboutPage() {
return ( return (
<HomeLayout {...baseOptions()} links={siteConfig.navLinks}> <HomeLayout {...baseOptions()} links={siteConfig.navLinks}>
<div className="min-h-screen font-sans text-foreground"> <div className="min-h-screen font-sans text-foreground">
<section className="relative text-center py-20 overflow-hidden"> <section className="relative overflow-hidden py-20 text-center">
<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="bg-grid-black/[0.05] dark:bg-grid-white/[0.05] absolute top-0 left-0 h-full w-full [mask-image:linear-gradient(to_bottom,white_10%,transparent_100%)]" />
<div className="container mx-auto px-4 relative z-10"> <div className="relative z-10 container mx-auto px-4">
<motion.div <motion.div
initial={{ opacity: 0, y: -50 }} initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }} 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"> <div className="mb-8 inline-flex items-center rounded-full border border-red-200 bg-red-50 px-4 py-2 text-red-700 transition-colors sm:h-8 dark:border-red-800 dark:bg-red-900/20 dark:text-red-300">
<Shield className="w-4 h-4 mr-2" /> <Shield className="mr-2 h-4 w-4" />
<span className="text-sm"> <span className="text-sm">For Security Research & Authorized Testing Only</span>
For Security Research & Authorized Testing Only
</span>
</div> </div>
<h1 className="text-5xl md:text-7xl font-bold mb-8"> <h1 className="mb-8 text-5xl font-bold md:text-7xl">
<span className="dark:text-white text-gray-900"> <span className="text-gray-900 dark:text-white">
MemShell MemShell
<LineShadowText className="italic" shadowColor={shadowColor}> <LineShadowText className="italic" shadowColor={shadowColor}>
Party Party
</LineShadowText> </LineShadowText>
</span> </span>
</h1> </h1>
<p className="text-xl md:text-2xl mb-12 max-w-4xl mx-auto leading-relaxed text-muted-foreground"> <p className="mx-auto mb-12 max-w-4xl text-xl leading-relaxed text-muted-foreground md:text-2xl">
A self-hosted, visual platform for one-click generation of Java A self-hosted, visual platform for one-click generation of Java memory shells for
memory shells for common middleware and frameworks. The ultimate common middleware and frameworks. The ultimate learning platform for security
learning platform for security researchers. researchers.
</p> </p>
</motion.div> </motion.div>
</div> </div>
@@ -111,24 +111,17 @@ export default function AboutPage() {
<motion.div <motion.div
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
className="container mx-auto px-4 mb-8" className="container mx-auto mb-8 px-4"
> >
<Alert className="border-green-500 bg-green-50 dark:bg-green-900/20 w-auto"> <Alert className="w-auto border-green-500 bg-green-50 dark:bg-green-900/20">
<AlertCircle className="h-4 w-4 text-green-600 dark:text-green-400" /> <AlertCircle className="h-4 w-4 text-green-600 dark:text-green-400" />
<AlertDescription className="flex items-center justify-between flex-wrap gap-4"> <AlertDescription className="flex flex-wrap items-center justify-between gap-4">
<span className="text-green-800 dark:text-green-300"> <span className="text-green-800 dark:text-green-300">
New version {updateInfo.latestVersion} is available! (Current:{" "} New version {updateInfo.latestVersion} is available! (Current:{" "}
{updateInfo.currentVersion}) {updateInfo.currentVersion})
</span> </span>
<a <a href={siteConfig.latestRelease} target="_blank" rel="noopener noreferrer">
href={siteConfig.latestRelease} <Button size="sm" className="bg-green-600 text-white hover:bg-green-700">
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" /> <Download className="mr-2 h-4 w-4" />
View Release View Release
</Button> </Button>
@@ -144,42 +137,28 @@ export default function AboutPage() {
animate="visible" animate="visible"
className="container mx-auto px-4 py-16" className="container mx-auto px-4 py-16"
> >
<div className="grid md:grid-cols-2 gap-8 max-w-6xl mx-auto"> <div className="mx-auto grid max-w-6xl gap-8 md:grid-cols-2">
<motion.div variants={itemVariants}> <motion.div variants={itemVariants}>
<Card className="h-full"> <Card className="h-full">
<CardContent className="p-6"> <CardContent className="p-6">
<div className="flex items-center mb-4"> <div className="mb-4 flex items-center">
<Package className="w-6 h-6 mr-3 text-primary" /> <Package className="mr-3 h-6 w-6 text-primary" />
<h2 className="text-2xl font-bold">Version</h2> <h2 className="text-2xl font-bold">Version</h2>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<div className="flex justify-between items-center py-2 border-b border-border/50"> <div className="flex items-center justify-between border-b border-border/50 py-2">
<span className="text-muted-foreground"> <span className="text-muted-foreground">Current Version</span>
Current Version <Badge variant="secondary">{updateInfo?.currentVersion || "v0.0.0"}</Badge>
</span>
<Badge variant="secondary">
{updateInfo?.currentVersion || "v0.0.0"}
</Badge>
</div> </div>
<div className="flex justify-between items-center py-2 border-b border-border/50"> <div className="flex items-center justify-between border-b border-border/50 py-2">
<span className="text-muted-foreground"> <span className="text-muted-foreground">Latest Version</span>
Latest Version {isPending && <span className="text-sm text-gray-500">Checking...</span>}
</span> {error && <Badge variant="destructive">{error.message}</Badge>}
{isPending && (
<span className="text-sm text-gray-500">
Checking...
</span>
)}
{error && (
<Badge variant="destructive">{error.message}</Badge>
)}
{updateInfo && !error && ( {updateInfo && !error && (
<Badge variant="outline"> <Badge variant="outline">{updateInfo.latestVersion}</Badge>
{updateInfo.latestVersion}
</Badge>
)} )}
</div> </div>
<div className="flex justify-between items-center py-2 border-b border-border/50"> <div className="flex items-center justify-between border-b border-border/50 py-2">
<span className="text-muted-foreground">License</span> <span className="text-muted-foreground">License</span>
<Badge variant="outline">MIT License</Badge> <Badge variant="outline">MIT License</Badge>
</div> </div>
@@ -196,23 +175,19 @@ export default function AboutPage() {
<motion.div variants={itemVariants}> <motion.div variants={itemVariants}>
<Card className="h-full"> <Card className="h-full">
<CardContent className="p-6"> <CardContent className="p-6">
<div className="flex items-center mb-4"> <div className="mb-4 flex items-center">
<User className="w-6 h-6 mr-3 text-primary" /> <User className="mr-3 h-6 w-6 text-primary" />
<h2 className="text-2xl font-bold">Author</h2> <h2 className="text-2xl font-bold">Author</h2>
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Avatar className="w-16 h-16"> <Avatar className="h-16 w-16">
<AvatarImage src="https://cdn.jsdelivr.net/gh/ReaJason/blog_imgs/default/blog_avatar.jpg" /> <AvatarImage src="https://cdn.jsdelivr.net/gh/ReaJason/blog_imgs/default/blog_avatar.jpg" />
<AvatarFallback>RJ</AvatarFallback> <AvatarFallback>RJ</AvatarFallback>
</Avatar> </Avatar>
<div> <div>
<h3 className="font-semibold text-lg"> <h3 className="text-lg font-semibold">{siteConfig.author}</h3>
{siteConfig.author} <p className="text-sm text-muted-foreground">{siteConfig.authorIntro}</p>
</h3>
<p className="text-sm text-muted-foreground">
{siteConfig.authorIntro}
</p>
</div> </div>
</div> </div>
<div className="space-y-2 pt-2"> <div className="space-y-2 pt-2">
@@ -220,24 +195,24 @@ export default function AboutPage() {
href={siteConfig.blog} href={siteConfig.blog}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-2 text-muted-foreground hover:text-primary transition-colors" className="flex items-center gap-2 text-muted-foreground transition-colors hover:text-primary"
> >
<Globe className="w-4 h-4" /> <Globe className="h-4 w-4" />
<span className="text-sm">reajason.eu.org</span> <span className="text-sm">reajason.eu.org</span>
<ExternalLink className="w-3 h-3" /> <ExternalLink className="h-3 w-3" />
</a> </a>
<a <a
href={siteConfig.authorGithub} href={siteConfig.authorGithub}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-2 text-muted-foreground hover:text-primary transition-colors" className="flex items-center gap-2 text-muted-foreground transition-colors hover:text-primary"
> >
<Icons.gitHub className="w-4 h-4" /> <Icons.gitHub className="h-4 w-4" />
<span className="text-sm">github.com/ReaJason</span> <span className="text-sm">github.com/ReaJason</span>
<ExternalLink className="w-3 h-3" /> <ExternalLink className="h-3 w-3" />
</a> </a>
<div className="flex items-center gap-2 text-muted-foreground"> <div className="flex items-center gap-2 text-muted-foreground">
<Mail className="w-4 h-4" /> <Mail className="h-4 w-4" />
<span className="text-sm">Contact via GitHub</span> <span className="text-sm">Contact via GitHub</span>
</div> </div>
</div> </div>
@@ -254,65 +229,62 @@ export default function AboutPage() {
transition={{ duration: 0.8, delay: 0.6 }} transition={{ duration: 0.8, delay: 0.6 }}
className="container mx-auto px-4 py-16" className="container mx-auto px-4 py-16"
> >
<div className="max-w-6xl mx-auto"> <div className="mx-auto max-w-6xl">
<div className="text-center mb-12"> <div className="mb-12 text-center">
<h2 className="text-3xl font-bold mb-4">Resources & Links</h2> <h2 className="mb-4 text-3xl font-bold">Resources & Links</h2>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Explore documentation and contribute to the project Explore documentation and contribute to the project
</p> </p>
</div> </div>
<div className="grid md:grid-cols-3 gap-6"> <div className="grid gap-6 md:grid-cols-3">
<Card className="group hover:shadow-lg transition-shadow"> <Card className="group transition-shadow hover:shadow-lg">
<CardContent className="p-6"> <CardContent className="p-6">
<Code className="w-10 h-10 mb-4 text-primary group-hover:scale-110 transition-transform" /> <Code className="mb-4 h-10 w-10 text-primary transition-transform group-hover:scale-110" />
<h3 className="font-semibold text-lg mb-2">Documentation</h3> <h3 className="mb-2 text-lg font-semibold">Documentation</h3>
<p className="text-sm text-muted-foreground mb-4"> <p className="mb-4 text-sm text-muted-foreground">
Comprehensive guides and API references for using Comprehensive guides and API references for using MemShellParty effectively.
MemShellParty effectively.
</p> </p>
<Link <Link
className="text-primary hover:underline text-sm font-medium flex items-center gap-1" className="flex items-center gap-1 text-sm font-medium text-primary hover:underline"
to="/docs" to="/docs"
> >
Read Docs <ExternalLink className="w-3 h-3" /> Read Docs <ExternalLink className="h-3 w-3" />
</Link> </Link>
</CardContent> </CardContent>
</Card> </Card>
<Card className="group hover:shadow-lg transition-shadow"> <Card className="group transition-shadow hover:shadow-lg">
<CardContent className="p-6"> <CardContent className="p-6">
<Icons.gitHub className="w-10 h-10 mb-4 text-primary group-hover:scale-110 transition-transform" /> <Icons.gitHub className="mb-4 h-10 w-10 text-primary transition-transform group-hover:scale-110" />
<h3 className="font-semibold text-lg mb-2">Source Code</h3> <h3 className="mb-2 text-lg font-semibold">Source Code</h3>
<p className="text-sm text-muted-foreground mb-4"> <p className="mb-4 text-sm text-muted-foreground">
View the source code, report issues, and contribute to the View the source code, report issues, and contribute to the development.
development.
</p> </p>
<a <a
href={siteConfig.github} href={siteConfig.github}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-primary hover:underline text-sm font-medium flex items-center gap-1" className="flex items-center gap-1 text-sm font-medium text-primary hover:underline"
> >
View Repository <ExternalLink className="w-3 h-3" /> View Repository <ExternalLink className="h-3 w-3" />
</a> </a>
</CardContent> </CardContent>
</Card> </Card>
<Card className="group hover:shadow-lg transition-shadow"> <Card className="group transition-shadow hover:shadow-lg">
<CardContent className="p-6"> <CardContent className="p-6">
<Heart className="w-10 h-10 mb-4 text-primary group-hover:scale-110 transition-transform" /> <Heart className="mb-4 h-10 w-10 text-primary transition-transform group-hover:scale-110" />
<h3 className="font-semibold text-lg mb-2">Support</h3> <h3 className="mb-2 text-lg font-semibold">Support</h3>
<p className="text-sm text-muted-foreground mb-4"> <p className="mb-4 text-sm text-muted-foreground">
Star the project on GitHub and share it with the security Star the project on GitHub and share it with the security community.
community.
</p> </p>
<a <a
href={siteConfig.github} href={siteConfig.github}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-primary hover:underline text-sm font-medium flex items-center gap-1" className="flex items-center gap-1 text-sm font-medium text-primary hover:underline"
> >
Star on GitHub <ExternalLink className="w-3 h-3" /> Star on GitHub <ExternalLink className="h-3 w-3" />
</a> </a>
</CardContent> </CardContent>
</Card> </Card>
@@ -320,8 +292,8 @@ export default function AboutPage() {
</div> </div>
</motion.section> </motion.section>
<footer className="border-t py-8 mt-16"> <footer className="mt-16 border-t py-8">
<div className="container mx-auto px-4 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between text-center sm:text-left"> <div className="container mx-auto flex flex-col gap-4 px-4 text-center sm:flex-row sm:items-center sm:justify-between sm:text-left">
<div> <div>
<p className="text-sm font-semibold">{siteConfig.name}</p> <p className="text-sm font-semibold">{siteConfig.name}</p>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
@@ -330,7 +302,7 @@ export default function AboutPage() {
href={siteConfig.blog} href={siteConfig.blog}
rel="noreferrer noopener" rel="noreferrer noopener"
target="_blank" target="_blank"
className="font-medium hover:text-primary transition-colors" className="font-medium transition-colors hover:text-primary"
> >
{siteConfig.author} {siteConfig.author}
</a> </a>
+13 -29
View File
@@ -5,6 +5,7 @@ import { useCallback, useRef, useState } 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 { 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";
@@ -26,6 +27,7 @@ import {
useYupValidationResolver, useYupValidationResolver,
} from "@/types/schema"; } from "@/types/schema";
import { transformToPostData } from "@/utils/transformer"; import { transformToPostData } from "@/utils/transformer";
import { baseOptions } from "../lib/layout.shared"; import { baseOptions } from "../lib/layout.shared";
const homeLayoutOptions = baseOptions(); const homeLayoutOptions = baseOptions();
@@ -63,14 +65,11 @@ const fetchJson = async <T,>(url: string): Promise<T> => {
return response.json() as Promise<T>; return response.json() as Promise<T>;
}; };
const fetchServerConfig = () => const fetchServerConfig = () => fetchJson<ServerConfig>(`${env.API_URL}/api/config/servers`);
fetchJson<ServerConfig>(`${env.API_URL}/api/config/servers`);
const fetchMainConfig = () => const fetchMainConfig = () => fetchJson<MainConfig>(`${env.API_URL}/api/config`);
fetchJson<MainConfig>(`${env.API_URL}/api/config`);
const fetchPackerConfig = () => const fetchPackerConfig = () => fetchJson<PackerConfig>(`${env.API_URL}/api/config/packers`);
fetchJson<PackerConfig>(`${env.API_URL}/api/config/packers`);
export default function MemShellPage() { export default function MemShellPage() {
const { data: serverConfig } = useQuery<ServerConfig>({ const { data: serverConfig } = useQuery<ServerConfig>({
@@ -95,9 +94,7 @@ export default function MemShellPage() {
}); });
const [packResult, setPackResult] = useState<string | undefined>(); const [packResult, setPackResult] = useState<string | undefined>();
const [allPackResults, setAllPackResults] = useState< const [allPackResults, setAllPackResults] = useState<Map<string, string> | undefined>();
Map<string, string> | undefined
>();
const [generateResult, setGenerateResult] = useState<MemShellResult>(); const [generateResult, setGenerateResult] = useState<MemShellResult>();
const [packMethod, setPackMethod] = useState<string>(""); const [packMethod, setPackMethod] = useState<string>("");
const submitLockRef = useRef(false); const submitLockRef = useRef(false);
@@ -125,9 +122,7 @@ export default function MemShellPage() {
setPackMethod(data.packingMethod); setPackMethod(data.packingMethod);
toast.success(t("toast.generateSuccess")); toast.success(t("toast.generateSuccess"));
} catch (error) { } catch (error) {
toast.error( toast.error(t("toast.generateError", { error: (error as Error).message }));
t("toast.generateError", { error: (error as Error).message }),
);
} }
}, },
[t], [t],
@@ -149,23 +144,12 @@ export default function MemShellPage() {
return ( return (
<HomeLayout {...homeLayoutOptions} links={siteConfig.navLinks}> <HomeLayout {...homeLayoutOptions} links={siteConfig.navLinks}>
<div className="container mx-auto max-w-8xl p-6"> <div className="max-w-8xl container mx-auto p-6">
<form <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6 xl:flex-row">
onSubmit={form.handleSubmit(onSubmit)} <div className="flex w-full flex-col gap-2 xl:w-1/2">
className="flex flex-col xl:flex-row gap-6" <MainConfigCard servers={serverConfig} mainConfig={mainConfig} form={form} />
>
<div className="w-full xl:w-1/2 flex flex-col gap-2">
<MainConfigCard
servers={serverConfig}
mainConfig={mainConfig}
form={form}
/>
<PackageConfigCard packerConfig={packerConfig} form={form} /> <PackageConfigCard packerConfig={packerConfig} form={form} />
<Button <Button className="w-full" type="submit" disabled={form.formState.isSubmitting}>
className="w-full"
type="submit"
disabled={form.formState.isSubmitting}
>
{form.formState.isSubmitting ? ( {form.formState.isSubmitting ? (
<LoaderCircle className="animate-spin" /> <LoaderCircle className="animate-spin" />
) : ( ) : (
@@ -174,7 +158,7 @@ export default function MemShellPage() {
{t("memshell:buttons.generate")} {t("memshell:buttons.generate")}
</Button> </Button>
</div> </div>
<div className="w-full xl:w-1/2 flex flex-col gap-2"> <div className="flex w-full flex-col gap-2 xl:w-1/2">
<ShellResult <ShellResult
packMethod={packMethod} packMethod={packMethod}
generateResult={generateResult} generateResult={generateResult}
+12 -27
View File
@@ -1,3 +1,6 @@
import type { APIErrorResponse, PackerConfig, ServerConfig } from "@/types/memshell";
import type { ProbeShellGenerateResponse, ProbeShellResult } from "@/types/probeshell";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { HomeLayout } from "fumadocs-ui/layouts/home"; import { HomeLayout } from "fumadocs-ui/layouts/home";
import { LoaderCircle, WandSparklesIcon } from "lucide-react"; import { LoaderCircle, WandSparklesIcon } from "lucide-react";
@@ -5,27 +8,20 @@ import { useRef, useState } 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 { toast } from "sonner"; import { toast } from "sonner";
import MainConfigCard from "@/components/probeshell/main-config-card"; import MainConfigCard from "@/components/probeshell/main-config-card";
import PackageConfigCard from "@/components/probeshell/package-config-card"; import PackageConfigCard from "@/components/probeshell/package-config-card";
import ShellResult from "@/components/probeshell/shell-result"; import ShellResult from "@/components/probeshell/shell-result";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { env } from "@/config"; import { env } from "@/config";
import { siteConfig } from "@/lib/config"; import { siteConfig } from "@/lib/config";
import type {
APIErrorResponse,
PackerConfig,
ServerConfig,
} from "@/types/memshell";
import type {
ProbeShellGenerateResponse,
ProbeShellResult,
} from "@/types/probeshell";
import { import {
type ProbeShellFormSchema, type ProbeShellFormSchema,
probeShellFormSchema, probeShellFormSchema,
useYupValidationProbeResolver, useYupValidationProbeResolver,
} from "@/types/schema"; } from "@/types/schema";
import { transformToProbePostData } from "@/utils/transformer"; import { transformToProbePostData } from "@/utils/transformer";
import { baseOptions } from "../lib/layout.shared"; import { baseOptions } from "../lib/layout.shared";
export default function ProbeShellGenerator() { export default function ProbeShellGenerator() {
@@ -63,9 +59,7 @@ export default function ProbeShellGenerator() {
}); });
const [packResult, setPackResult] = useState<string | undefined>(); const [packResult, setPackResult] = useState<string | undefined>();
const [allPackResults, setAllPackResults] = useState< const [allPackResults, setAllPackResults] = useState<Map<string, string> | undefined>();
Map<string, string> | undefined
>();
const [generateResult, setGenerateResult] = useState<ProbeShellResult>(); const [generateResult, setGenerateResult] = useState<ProbeShellResult>();
const [packMethod, setPackMethod] = useState<string>(""); const [packMethod, setPackMethod] = useState<string>("");
const submitLockRef = useRef(false); const submitLockRef = useRef(false);
@@ -97,28 +91,19 @@ export default function ProbeShellGenerator() {
setPackMethod(data.packingMethod); setPackMethod(data.packingMethod);
toast.success(t("toast.generateSuccess")); toast.success(t("toast.generateSuccess"));
} catch (error) { } catch (error) {
toast.error( toast.error(t("toast.generateError", { error: (error as Error).message }));
t("toast.generateError", { error: (error as Error).message }),
);
} finally { } finally {
submitLockRef.current = false; submitLockRef.current = false;
} }
}; };
return ( return (
<HomeLayout {...baseOptions()} links={siteConfig.navLinks}> <HomeLayout {...baseOptions()} links={siteConfig.navLinks}>
<div className="container mx-auto max-w-8xl p-6"> <div className="max-w-8xl container mx-auto p-6">
<form <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-6 xl:flex-row">
onSubmit={form.handleSubmit(onSubmit)} <div className="flex w-full flex-col gap-2 xl:w-1/2">
className="flex flex-col xl:flex-row gap-6"
>
<div className="w-full xl:w-1/2 flex flex-col gap-2">
<MainConfigCard form={form} servers={serverConfig} /> <MainConfigCard form={form} servers={serverConfig} />
<PackageConfigCard form={form} packerConfig={packerConfig} /> <PackageConfigCard form={form} packerConfig={packerConfig} />
<Button <Button className="w-full" type="submit" disabled={form.formState.isSubmitting}>
className="w-full"
type="submit"
disabled={form.formState.isSubmitting}
>
{form.formState.isSubmitting ? ( {form.formState.isSubmitting ? (
<LoaderCircle className="animate-spin" /> <LoaderCircle className="animate-spin" />
) : ( ) : (
@@ -127,7 +112,7 @@ export default function ProbeShellGenerator() {
{t("probeshell:buttons.generate")} {t("probeshell:buttons.generate")}
</Button> </Button>
</div> </div>
<div className="w-full xl:w-1/2 flex flex-col gap-2"> <div className="flex w-full flex-col gap-2 xl:w-1/2">
<ShellResult <ShellResult
packMethod={packMethod} packMethod={packMethod}
generateResult={generateResult} generateResult={generateResult}
+1 -7
View File
@@ -1,12 +1,6 @@
export type ProbeMethod = "ResponseBody" | "DNSLog" | "Sleep"; export type ProbeMethod = "ResponseBody" | "DNSLog" | "Sleep";
export type ProbeContent = export type ProbeContent = "BasicInfo" | "Server" | "OS" | "JDK" | "Bytecode" | "Command";
| "BasicInfo"
| "Server"
| "OS"
| "JDK"
| "Bytecode"
| "Command";
export interface ProbeConfig { export interface ProbeConfig {
probeMethod: string; probeMethod: string;
+25 -48
View File
@@ -1,7 +1,9 @@
import type { TFunction } from "i18next"; import type { TFunction } from "i18next";
import { useCallback } from "react";
import type { FieldErrors, ResolverResult } from "react-hook-form"; import type { FieldErrors, ResolverResult } from "react-hook-form";
import { useCallback } from "react";
import * as yup from "yup"; import * as yup from "yup";
import { ShellToolType } from "./memshell"; import { ShellToolType } from "./memshell";
export const memShellFormSchema = yup.object({ export const memShellFormSchema = yup.object({
@@ -49,20 +51,11 @@ const urlPatternIsNeeded = (shellType: string) => {
}; };
const isInvalidUrl = (urlPattern: string | undefined) => const isInvalidUrl = (urlPattern: string | undefined) =>
urlPattern === "/" || urlPattern === "/" || urlPattern === "/*" || !urlPattern?.startsWith("/") || !urlPattern;
urlPattern === "/*" ||
!urlPattern?.startsWith("/") ||
!urlPattern;
export const useYupValidationResolver = ( export const useYupValidationResolver = (validationSchema: yup.ObjectSchema<any>, t: TFunction) =>
validationSchema: yup.ObjectSchema<any>,
t: TFunction,
) =>
useCallback( useCallback(
async ( async (data: MemShellFormSchema, _context: any): Promise<ValidationResult> => {
data: MemShellFormSchema,
_context: any,
): Promise<ValidationResult> => {
try { try {
const values = (await validationSchema.validate(data, { const values = (await validationSchema.validate(data, {
abortEarly: false, abortEarly: false,
@@ -73,19 +66,13 @@ export const useYupValidationResolver = (
const serverVersion: keyof MemShellFormSchema = "serverVersion"; const serverVersion: keyof MemShellFormSchema = "serverVersion";
const errors = {} as any; const errors = {} as any;
if ( if (urlPatternIsNeeded(values?.shellType) && isInvalidUrl(values?.urlPattern)) {
urlPatternIsNeeded(values?.shellType) &&
isInvalidUrl(values?.urlPattern)
) {
errors[urlPattern] = { errors[urlPattern] = {
type: "custom", type: "custom",
message: t("memshell:tips.specificUrlPattern"), message: t("memshell:tips.specificUrlPattern"),
}; };
} }
if ( if (values.shellTool === ShellToolType.Custom && !values.shellClassBase64) {
values.shellTool === ShellToolType.Custom &&
!values.shellClassBase64
) {
errors[shellClassBase64] = { errors[shellClassBase64] = {
type: "custom", type: "custom",
message: t("memshell:tips.customShellClass"), message: t("memshell:tips.customShellClass"),
@@ -104,8 +91,7 @@ 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] = {
@@ -122,16 +108,13 @@ export const useYupValidationResolver = (
if (errors instanceof yup.ValidationError) { if (errors instanceof yup.ValidationError) {
return { return {
values: {}, values: {},
errors: errors.inner.reduce( errors: errors.inner.reduce((allErrors, currentError) => {
(allErrors, currentError) => { allErrors[currentError.path as keyof MemShellFormSchema] = {
allErrors[currentError.path as keyof MemShellFormSchema] = { type: currentError.type ?? "validation",
type: currentError.type ?? "validation", message: currentError.message,
message: currentError.message, };
}; return allErrors;
return allErrors; }, {} as FieldErrors<MemShellFormSchema>),
},
{} as FieldErrors<MemShellFormSchema>,
),
}; };
} }
@@ -179,10 +162,7 @@ export const useYupValidationProbeResolver = (
t: TFunction, t: TFunction,
) => ) =>
useCallback( useCallback(
async ( async (data: ProbeShellFormSchema, _context: any): Promise<ProbeValidationResult> => {
data: ProbeShellFormSchema,
_context: any,
): Promise<ProbeValidationResult> => {
try { try {
const values = (await validationSchema.validate(data, { const values = (await validationSchema.validate(data, {
abortEarly: false, abortEarly: false,
@@ -205,17 +185,14 @@ export const useYupValidationProbeResolver = (
if (errors instanceof yup.ValidationError) { if (errors instanceof yup.ValidationError) {
return { return {
values: {}, values: {},
errors: errors.inner.reduce( errors: errors.inner.reduce((allErrors, currentError) => {
(allErrors, currentError) => { allErrors[currentError.path as keyof ProbeShellFormSchema] = {
allErrors[currentError.path as keyof ProbeShellFormSchema] = { type: currentError.type ?? "validation",
type: currentError.type ?? "validation", message: currentError.message,
message: currentError.message, };
}; console.log(allErrors);
console.log(allErrors); return allErrors;
return allErrors; }, {} as FieldErrors<ProbeShellFormSchema>),
},
{} as FieldErrors<ProbeShellFormSchema>,
),
}; };
} }
+1 -5
View File
@@ -1,8 +1,4 @@
import type { import type { InjectorConfig, ShellConfig, ShellToolConfig } from "@/types/memshell";
InjectorConfig,
ShellConfig,
ShellToolConfig,
} from "@/types/memshell";
import type { ProbeConfig, ProbeContentConfig } from "@/types/probeshell"; import type { ProbeConfig, ProbeContentConfig } from "@/types/probeshell";
import type { MemShellFormSchema, ProbeShellFormSchema } from "@/types/schema"; import type { MemShellFormSchema, ProbeShellFormSchema } from "@/types/schema";
@@ -2,4 +2,4 @@
title: AbstractTranslet title: AbstractTranslet
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: Agent Jar title: Agent Jar
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: Base64 title: Base64
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: BCEL title: BCEL
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: BigInteger title: BigInteger
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: 表达式注入 title: 表达式注入
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: H2 JDBC URL title: H2 JDBC URL
--- ---
WIP WIP
@@ -2,4 +2,4 @@
title: Hessian 反序列化 title: Hessian 反序列化
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: Jar title: Jar
--- ---
WIP WIP
@@ -2,4 +2,4 @@
title: Java 原生反序列化 title: Java 原生反序列化
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: JSP title: JSP
--- ---
WIP WIP
+1 -1
View File
@@ -4,4 +4,4 @@ title: 脚本引擎注入
### Nashorn ScriptEngine ### Nashorn ScriptEngine
### Rhino ScriptEngine ### Rhino ScriptEngine
+1 -1
View File
@@ -2,4 +2,4 @@
title: 模板注入 title: 模板注入
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: XMLDecoder title: XMLDecoder
--- ---
WIP WIP
-1
View File
@@ -4,7 +4,6 @@ title: Java Agent 内存马
## Java Agent 原理 ## Java Agent 原理
## Java Agent 内存马实现方案 ## Java Agent 内存马实现方案
## 切点选取 ## 切点选取
+9 -6
View File
@@ -187,13 +187,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3. 添加 JXPathSpringGzipPacker、JXPathSpringGzipPackerJDK17 打包方式(GeoServer 漏洞注入) 3. 添加 JXPathSpringGzipPacker、JXPathSpringGzipPackerJDK17 打包方式(GeoServer 漏洞注入)
4. 添加 Base64URLEncoded 打包方式(配合回显马进行小马拉大马测试) 4. 添加 Base64URLEncoded 打包方式(配合回显马进行小马拉大马测试)
5. 支持回显马在进行自定义字节码执行时去除 Java 魔数流量特征 5. 支持回显马在进行自定义字节码执行时去除 Java 魔数流量特征
```http
```http
/path/code?payload=yv66vgAAADIBVQEAJ29yZy9hcGFj... /path/code?payload=yv66vgAAADIBVQEAJ29yZy9hcGFj...
``` ```
改为只需要如下方式 改为只需要如下方式
```http ```http
/path/code?payload=IBVQEAJ29yZy9hcGFj... /path/code?payload=IBVQEAJ29yZy9hcGFj...
``` ```
### Fixed ### Fixed
@@ -274,7 +277,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 简化 Shell base64 方法代码 - 简化 Shell base64 方法代码
- Gradle 更新至 8.14.2 - Gradle 更新至 8.14.2
- 参考 [General Gradle Best Practices](https://docs.gradle.org/current/userguide/best_practices_general.html),将构建脚本改为 - 参考 [General Gradle Best Practices](https://docs.gradle.org/current/userguide/best_practices_general.html),将构建脚本改为
Kotlin DSL Kotlin DSL
**Full Changelog:** [v1.9.0...v1.10.0](https://github.com/ReaJason/MemShellParty/compare/v1.9.0...v1.10.0) **Full Changelog:** [v1.9.0...v1.10.0](https://github.com/ReaJason/MemShellParty/compare/v1.9.0...v1.10.0)
@@ -284,7 +287,7 @@ Kotlin DSL
- 支持 TongWeb8 内存马生成 by @ReaJason - 支持 TongWeb8 内存马生成 by @ReaJason
- 通过 context 获取 webAppClassLoader,不再依赖 Thread.currentThread().getContextClassLoader() - 通过 context 获取 webAppClassLoader,不再依赖 Thread.currentThread().getContextClassLoader()
为请求线程,参考:[任意类加载环境下注入内存马](https://reajason.eu.org/writing/whichclassloaderforshell/) 为请求线程,参考:[任意类加载环境下注入内存马](https://reajason.eu.org/writing/whichclassloaderforshell/)
- 全面支持使用 ASM 生成 Agent(仅需 92.5 KB),并且可选 JDKAttacher 与 JREAttacher - 全面支持使用 ASM 生成 Agent(仅需 92.5 KB),并且可选 JDKAttacher 与 JREAttacher
- 支持命令执行自定义实现类,RuntimeExec or ForkAndExec - 支持命令执行自定义实现类,RuntimeExec or ForkAndExec
@@ -495,4 +498,4 @@ Kotlin DSL
- 支持 Tomcat、Jetty、WebLogic、GlassFish、JBoss、Resin 等 18 个中间件或框架的应用内存马 - 支持 Tomcat、Jetty、WebLogic、GlassFish、JBoss、Resin 等 18 个中间件或框架的应用内存马
- 支持 Filter、Servlet、Listener、NettyHandler、Agent 等常见内存马挂载类型 - 支持 Filter、Servlet、Listener、NettyHandler、Agent 等常见内存马挂载类型
- 支持哥斯拉、冰蝎、命令执行功能 - 支持哥斯拉、冰蝎、命令执行功能
- 支持 Base64、Jar、JSP、常见表达式、常见模板引擎、反序列化等打包方式 - 支持 Base64、Jar、JSP、常见表达式、常见模板引擎、反序列化等打包方式
+45 -46
View File
@@ -3,60 +3,59 @@ title: 适配情况
icon: Album icon: Album
--- ---
已兼容 Java6 ~ Java8、Java9、Java11、Java17、Java21 已兼容 Java6 ~ Java8、Java9、Java11、Java17、Java21
### 中间件以及框架 ### 中间件以及框架
| [Tomcat5 ~ 11](/docs/server-intro#tomcat) | [Jetty6 ~ 12](/docs/server-intro#jetty)| [GlassFish3 ~ 7](/docs/server-intro#glassfish) | [Payara5 ~ 6](/docs/server-intro#payara)| | [Tomcat5 ~ 11](/docs/server-intro#tomcat) | [Jetty6 ~ 12](/docs/server-intro#jetty) | [GlassFish3 ~ 7](/docs/server-intro#glassfish) | [Payara5 ~ 6](/docs/server-intro#payara) |
|----------------------|------------------------|----------------------|----------------------| | --------------------------------------------- | ------------------------------------------- | -------------------------------------------------- | -------------------------------------------- |
| Servlet | Servlet | Filter | Filter | | Servlet | Servlet | Filter | Filter |
| Filter | Filter | Listener | Listener | | Filter | Filter | Listener | Listener |
| Listener | Listener | Valve | Valve | | Listener | Listener | Valve | Valve |
| Valve | Handler | FilterChain - Agent | FilterChain - Agent | | Valve | Handler | FilterChain - Agent | FilterChain - Agent |
| ProxyValve | Customizer | ContextValve - Agent | ContextValve - Agent | | ProxyValve | Customizer | ContextValve - Agent | ContextValve - Agent |
| FilterChain - Agent | ServletHandler - Agent | | | | FilterChain - Agent | ServletHandler - Agent | | |
| ContextValve - Agent | | | | | ContextValve - Agent | | | |
| Upgrade | | | | | Upgrade | | | |
| [Resin3 ~ 4](/docs/server-intro#resin) | [SpringWebMVC](/docs/server-intro#springwebmvc) | [SpringWebFlux](/docs/server-intro#springwebflux) | [XXL-JOB](/docs/server-intro#xxljob) | | [Resin3 ~ 4](/docs/server-intro#resin) | [SpringWebMVC](/docs/server-intro#springwebmvc) | [SpringWebFlux](/docs/server-intro#springwebflux) | [XXL-JOB](/docs/server-intro#xxljob) |
|---------------------|--------------------------|-----------------|--------------| | ------------------------------------------ | ----------------------------------------------- | ------------------------------------------------- | ------------------------------------ |
| Servlet | Interceptor | WebFilter | NettyHandler | | Servlet | Interceptor | WebFilter | NettyHandler |
| Filter | ControllerHandler | HandlerMethod | | | Filter | ControllerHandler | HandlerMethod | |
| Listener | FrameworkServlet - Agent | HandlerFunction | | | Listener | FrameworkServlet - Agent | HandlerFunction | |
| FilterChain - Agent | | NettyHandler | | | FilterChain - Agent | | NettyHandler | |
| [JBossAS4 ~ 7](/docs/server-intro#jboss) | [JBossEAP6 ~ 8](/docs/server-intro#undertow)| [WildFly9 ~ 30](/docs/server-intro#wildfly) | [Undertow](/docs/server-intro#undertow)| | [JBossAS4 ~ 7](/docs/server-intro#jboss) | [JBossEAP6 ~ 8](/docs/server-intro#undertow) | [WildFly9 ~ 30](/docs/server-intro#wildfly) | [Undertow](/docs/server-intro#undertow) |
|----------------------|----------------------------|------------------------|------------------------| | -------------------------------------------- | ------------------------------------------------ | ----------------------------------------------- | --------------------------------------- |
| Filter | Filter | Servlet | Servlet | | Filter | Filter | Servlet | Servlet |
| Listener | Listener | Filter | Filter | | Listener | Listener | Filter | Filter |
| Valve | Valve(6) | Listener | Listener | | Valve | Valve(6) | Listener | Listener |
| ProxyValve | FilterChain - Agent (6) | ServletHandler - Agent | ServletHandler - Agent| | ProxyValve | FilterChain - Agent (6) | ServletHandler - Agent | ServletHandler - Agent |
| FilterChain - Agent | ContextValve - Agent (6) | | | | FilterChain - Agent | ContextValve - Agent (6) | | |
| ContextValve - Agent | ServletHandler - Agent (7) | | | | ContextValve - Agent | ServletHandler - Agent (7) | | |
| [WebSphere7 ~ 9](/docs/server-intro#websphere)| [WebLogic (10.3.6 ~ 14)](/docs/server-intro#weblogic) | | [WebSphere7 ~ 9](/docs/server-intro#websphere) | [WebLogic (10.3.6 ~ 14)](/docs/server-intro#weblogic) |
|-----------------------|-------------------------| | -------------------------------------------------- | ----------------------------------------------------- |
| Servlet | Servlet | | Servlet | Servlet |
| Filter | Filter | | Filter | Filter |
| Listener | Listener | | Listener | Listener |
| FilterManager - Agent | ServletContext - Agent | | FilterManager - Agent | ServletContext - Agent |
| [BES (9.5.x)](/docs/server-intro#bes)| [TongWeb6 ~ 8](/docs/server-intro#tongweb) | [InforSuite AS (9 ~ 10)](/docs/server-intro#inforsuite) | | [BES (9.5.x)](/docs/server-intro#bes) | [TongWeb6 ~ 8](/docs/server-intro#tongweb) | [InforSuite AS (9 ~ 10)](/docs/server-intro#inforsuite) |
|----------------------|----------------------|------------------------| | ------------------------------------- | ---------------------------------------------- | ------------------------------------------------------- |
| Filter | Filter | Filter | | Filter | Filter | Filter |
| Listener | Listener | Listener | | Listener | Listener | Listener |
| Valve | Valve | Valve | | Valve | Valve | Valve |
| FilterChain - Agent | FilterChain - Agent | FilterChain - Agent | | FilterChain - Agent | FilterChain - Agent | FilterChain - Agent |
| ContextValve - Agent | ContextValve - Agent | ContextValve - Agent | | ContextValve - Agent | ContextValve - Agent | ContextValve - Agent |
| [Apusic AS (9 ~ 10)](/docs/server-intro#apusic) | [Primeton (6.5)](/docs/server-intro#primeton)| | [Apusic AS (9 ~ 10)](/docs/server-intro#apusic) | [Primeton (6.5)](/docs/server-intro#primeton) |
|---------------------|----------------------| | ----------------------------------------------- | --------------------------------------------- |
| Servlet | Filter | | Servlet | Filter |
| Filter | Listener | | Filter | Listener |
| Listener | Valve | | Listener | Valve |
| FilterChain - Agent | FilterChain - Agent | | FilterChain - Agent | FilterChain - Agent |
| | ContextValve - Agent | | | ContextValve - Agent |
### 内存马功能 ### 内存马功能
@@ -86,4 +85,4 @@ icon: Album
- [x] [Agent](/docs/agent-jar) - [x] [Agent](/docs/agent-jar)
- [x] XXL-JOB Executor - [x] XXL-JOB Executor
- [ ] JNDI - [ ] JNDI
- [ ] 其他常见反序列化 - [ ] 其他常见反序列化
+8 -7
View File
@@ -26,8 +26,8 @@ inject(context, shell);
内存马类会放进所增强类的 ClassLoader 中,部分中间件会存在模块隔离,无法直接使用部分类,例如 `java.util.Base64`、 内存马类会放进所增强类的 ClassLoader 中,部分中间件会存在模块隔离,无法直接使用部分类,例如 `java.util.Base64`、
`javax.crypto.Cipher`。 `javax.crypto.Cipher`。
| 挂载类型 | 参考实现 | | 挂载类型 | 参考实现 |
|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Servlet/JakartaServlet | [GodzillaServlet](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaServlet.java) | | Servlet/JakartaServlet | [GodzillaServlet](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaServlet.java) |
| Filter/JakartaFilter | [GodzillaFilter](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaFilter.java) | | Filter/JakartaFilter | [GodzillaFilter](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaFilter.java) |
| Listener/JakartaListener | [GodzillaListener](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaListener.java) | | Listener/JakartaListener | [GodzillaListener](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaListener.java) |
@@ -46,14 +46,15 @@ inject(context, shell);
| (WAS)AgentFilterManager | [Godzilla](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/Godzilla.java) | | (WAS)AgentFilterManager | [Godzilla](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/Godzilla.java) |
| (WebLogic)AgentServletContext | [Godzilla](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/Godzilla.java) | | (WebLogic)AgentServletContext | [Godzilla](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/Godzilla.java) |
| (Undertow)AgentServletHandler | [GodzillaUndertowServletHandler](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaUndertowServletHandler.java) | | (Undertow)AgentServletHandler | [GodzillaUndertowServletHandler](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaUndertowServletHandler.java) |
| (Jetty)Handler | [GodzillaJettyHandler](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaJettyHandler.java) | | (Jetty)Handler | [GodzillaJettyHandler](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaJettyHandler.java) |
| (Jetty)Customizer | [GodzillaJettyCustomizer](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaJettyCustomizer.java) | | (Jetty)Customizer | [GodzillaJettyCustomizer](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaJettyCustomizer.java) |
| (Struct2)Action | [GodzillaStruct2Action](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaStruct2Action.java) | | (Struct2)Action | [GodzillaStruct2Action](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/godzilla/GodzillaStruct2Action.java) |
| (Tomcat)Upgrade | [CommandUpgrade](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandUpgrade.java) | | (Tomcat)Upgrade | [CommandUpgrade](https://github.com/ReaJason/MemShellParty/blob/master/generator/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandUpgrade.java) |
### 参考步骤 ### 参考步骤
1. 执行 `git clone https://github.com/ReaJason/MemShellParty.git` 下载当前项目到本地 1. 执行 `git clone https://github.com/ReaJason/MemShellParty.git` 下载当前项目到本地
2. 在 memshell/src/main/java/com/reajason/javaweb/memshell/shelltool 创建 custom 目录进行自定义内存马的编写 2. 在 memshell/src/main/java/com/reajason/javaweb/memshell/shelltool 创建 custom 目录进行自定义内存马的编写
3. 执行 `./gradlew :generator:compileJava` 或 `.\gradlew.bat :generator:compileJava` 3. 执行 `./gradlew :generator:compileJava` 或 `.\gradlew.bat :generator:compileJava`
4. 在 generator/build/classes/java/main/com/reajason/javaweb/memshell/shelltool/custom 下可以找到编译好的类文件 4. 在 generator/build/classes/java/main/com/reajason/javaweb/memshell/shelltool/custom 下可以找到编译好的类文件
5. 在生成界面,选择目标服务 - Custom - 挂载类型,上传 class 文件,选择打包方式并生成 5. 在生成界面,选择目标服务 - Custom - 挂载类型,上传 class 文件,选择打包方式并生成
+1 -1
View File
@@ -6,4 +6,4 @@ icon: CircleAlert
Hey there! Fumadocs is the docs framework that also works on React Router! Hey there! Fumadocs is the docs framework that also works on React Router!
## Heading ## Heading
+5 -4
View File
@@ -116,6 +116,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
``` ```
调试所用的打印代码默认是写在模板中的,因此当关闭调试模式的时候,我们会使用字节码修改技术对打印调试信息的函数调用进行删除,详细实现可参考:[LogRemoveMethodVisitor.java](https://github.com/ReaJason/MemShellParty/blob/master/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/LogRemoveMethodVisitor.java),也就是把以下三种函数调用给去除 调试所用的打印代码默认是写在模板中的,因此当关闭调试模式的时候,我们会使用字节码修改技术对打印调试信息的函数调用进行删除,详细实现可参考:[LogRemoveMethodVisitor.java](https://github.com/ReaJason/MemShellParty/blob/master/memshell-party-common/src/main/java/com/reajason/javaweb/buddy/LogRemoveMethodVisitor.java),也就是把以下三种函数调用给去除
```java ```java
System.out.println(msg) //printf 还不支持) System.out.println(msg) //printf 还不支持)
@@ -137,7 +138,8 @@ Logger.info(msg) // (java.util)
当开启回显后,会将注入器字节码放置到回显马中,返回一个目标服务类型的回显马,代码执行顺序为:回显马运行 -> 注入器注入 -> 挂载内存马。 当开启回显后,会将注入器字节码放置到回显马中,返回一个目标服务类型的回显马,代码执行顺序为:回显马运行 -> 注入器注入 -> 挂载内存马。
<Callout title="额外注意" type="warn"> <Callout title="额外注意" type="warn">
由于回显马需要从当前线程获取 request 和 response 对象,因此跨线程 RCE 的环境下,无法回显,根据代码执行顺序,无法回显的环境,开启回显模式之后注入器也不会进行注入动作,因此支持回显马但无法回显的环境,请一定不要开启回显模式。 由于回显马需要从当前线程获取 request 和 response 对象,因此跨线程 RCE
的环境下,无法回显,根据代码执行顺序,无法回显的环境,开启回显模式之后注入器也不会进行注入动作,因此支持回显马但无法回显的环境,请一定不要开启回显模式。
</Callout> </Callout>
在确认了内存马注入成功后,如果发现连不上,我们需要调整我们的内存马,将错误信息带出,方便排查,内置的 Godzilla 已经默认支持了。 在确认了内存马注入成功后,如果发现连不上,我们需要调整我们的内存马,将错误信息带出,方便排查,内置的 Godzilla 已经默认支持了。
@@ -193,10 +195,10 @@ private String getErrorMessage(Throwable throwable) { // [!code ++]
} // [!code ++] } // [!code ++]
``` ```
### 绕过模块限制 ### 绕过模块限制
JDK9+ 有了模块化系统,严格限制各大函数的调用。注入器在注入内存马时需要使用反射调用 defineClass,不是 java.base 模块无法调用,开启绕过模块限制会在注入器自动插入绕过模块限制的代码。也就是如下这坨代码: JDK9+ 有了模块化系统,严格限制各大函数的调用。注入器在注入内存马时需要使用反射调用 defineClass,不是 java.base 模块无法调用,开启绕过模块限制会在注入器自动插入绕过模块限制的代码。也就是如下这坨代码:
```java ```java
try { try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
@@ -229,7 +231,6 @@ try {
3. javac 去除调试信息:6658 长度 3. javac 去除调试信息:6658 长度
4. ASM SKIP_DEBUG**6349** 长度 4. ASM SKIP_DEBUG**6349** 长度
#### Javassist 去除属性 #### Javassist 去除属性
由于 Javassist 被用于各类反序列化字节码工具中,因此使用最为广泛,以下字节码缩小代码被广为流传 由于 Javassist 被用于各类反序列化字节码工具中,因此使用最为广泛,以下字节码缩小代码被广为流传
@@ -324,4 +325,4 @@ static {
} }
``` ```
这样使得部分漏洞 sink 点为 `Class.forName(name, true, classLoader);` 的场景也能正常触发内存马注入了。 这样使得部分漏洞 sink 点为 `Class.forName(name, true, classLoader);` 的场景也能正常触发内存马注入了。
+1 -1
View File
@@ -4,4 +4,4 @@ title: Jetty
## Handler 内存马 ## Handler 内存马
## Customizer 内存马 ## Customizer 内存马
+1 -1
View File
@@ -4,4 +4,4 @@ title: Tomcat Valve
## Valve 内存马 ## Valve 内存马
## ProxyValve 内存马 ## ProxyValve 内存马
@@ -104,7 +104,7 @@ public class WebSocketConfig implements ServerApplicationConfig {
.build(); .build();
result.add(config); result.add(config);
return result; return result;
} }
@@ -147,6 +147,7 @@ public class WsSci implements ServletContainerInitializer {
``` ```
代码中有两个关键的地方: 代码中有两个关键的地方:
1. init 方法中,注册的 WsServerContainer 对象会被放入 ServletContext 中,key 为 "javax.websocket.server.ServerContainer",高版本为 "jakarta.websocket.server.ServerContainer"。 1. init 方法中,注册的 WsServerContainer 对象会被放入 ServletContext 中,key 为 "javax.websocket.server.ServerContainer",高版本为 "jakarta.websocket.server.ServerContainer"。
2. 调用 WsServerContainer 的 addEndpoint 方法注册 Endpoint。 2. 调用 WsServerContainer 的 addEndpoint 方法注册 Endpoint。
@@ -218,7 +219,7 @@ public class WsFilter implements Filter {
} }
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (this.sc.areEndpointsRegistered() if (this.sc.areEndpointsRegistered()
&& UpgradeUtil.isWebSocketUpgradeRequest(request, response)) { && UpgradeUtil.isWebSocketUpgradeRequest(request, response)) {
HttpServletRequest req = (HttpServletRequest)request; HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response; HttpServletResponse resp = (HttpServletResponse)response;
@@ -237,8 +238,8 @@ public class WsFilter implements Filter {
public class UpgradeUtil { public class UpgradeUtil {
public static boolean isWebSocketUpgradeRequest(ServletRequest request, ServletResponse response) { public static boolean isWebSocketUpgradeRequest(ServletRequest request, ServletResponse response) {
return request instanceof HttpServletRequest return request instanceof HttpServletRequest
&& response instanceof HttpServletResponse && response instanceof HttpServletResponse
&& headerContainsToken((HttpServletRequest)request, "Upgrade", "websocket") // [!code highlight] && headerContainsToken((HttpServletRequest)request, "Upgrade", "websocket") // [!code highlight]
&& "GET".equals(((HttpServletRequest)request).getMethod()); && "GET".equals(((HttpServletRequest)request).getMethod());
} }
@@ -372,4 +373,4 @@ private void inject(Object context, Object obj) throws Exception {
## 相关文档 ## 相关文档
- [Command 内存马使用教程](/docs/shelltool/command) - 了解如何生成和使用 WebSocket 命令执行内存马 - [Command 内存马使用教程](/docs/shelltool/command) - 了解如何生成和使用 WebSocket 命令执行内存马
+1 -1
View File
@@ -2,4 +2,4 @@
title: 介绍 title: 介绍
--- ---
打包 打包
+1 -1
View File
@@ -2,4 +2,4 @@
title: 核心配置项 title: 核心配置项
--- ---
WIP WIP
+11 -11
View File
@@ -17,7 +17,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -43,7 +43,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -69,7 +69,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -95,7 +95,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -121,7 +121,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -147,7 +147,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -173,7 +173,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -199,7 +199,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -225,7 +225,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -251,7 +251,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
@@ -277,7 +277,7 @@ Hello World
</Cards> </Cards>
```ts ```ts
console.log('I love React!'); console.log("I love React!");
``` ```
### Heading ### Heading
+1 -1
View File
@@ -2,4 +2,4 @@
title: Apusic title: Apusic
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: GlassFish title: GlassFish
--- ---
WIP WIP
+1 -1
View File
@@ -2,4 +2,4 @@
title: Jetty title: Jetty
--- ---
WIP WIP

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