mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-24 16:01:52 +08:00
style: fmt code
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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,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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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")}: </span>
|
||||||
{t("common:packerMethod")}:
|
|
||||||
</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) => (
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,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}
|
||||||
|
|||||||
@@ -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,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">) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
@@ -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_")) {
|
||||||
|
|||||||
@@ -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,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, {
|
||||||
|
|||||||
@@ -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,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,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
@@ -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",
|
||||||
|
|||||||
@@ -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,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) {
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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}
|
||||||
|
|||||||
@@ -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,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
@@ -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,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";
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ title: Java Agent 内存马
|
|||||||
|
|
||||||
## Java Agent 原理
|
## Java Agent 原理
|
||||||
|
|
||||||
|
|
||||||
## Java Agent 内存马实现方案
|
## Java Agent 内存马实现方案
|
||||||
|
|
||||||
## 切点选取
|
## 切点选取
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -3,60 +3,59 @@ title: 适配情况
|
|||||||
icon: Album
|
icon: Album
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
已兼容 Java6 ~ Java8、Java9、Java11、Java17、Java21
|
已兼容 Java6 ~ Java8、Java9、Java11、Java17、Java21
|
||||||
|
|
||||||
### 中间件以及框架
|
### 中间件以及框架
|
||||||
|
|
||||||
| [Tomcat(5 ~ 11)](/docs/server-intro#tomcat) | [Jetty(6 ~ 12)](/docs/server-intro#jetty)| [GlassFish(3 ~ 7)](/docs/server-intro#glassfish) | [Payara(5 ~ 6)](/docs/server-intro#payara)|
|
| [Tomcat(5 ~ 11)](/docs/server-intro#tomcat) | [Jetty(6 ~ 12)](/docs/server-intro#jetty) | [GlassFish(3 ~ 7)](/docs/server-intro#glassfish) | [Payara(5 ~ 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 | | | |
|
||||||
|
|
||||||
| [Resin(3 ~ 4)](/docs/server-intro#resin) | [SpringWebMVC](/docs/server-intro#springwebmvc) | [SpringWebFlux](/docs/server-intro#springwebflux) | [XXL-JOB](/docs/server-intro#xxljob) |
|
| [Resin(3 ~ 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 | |
|
||||||
|
|
||||||
| [JBossAS(4 ~ 7)](/docs/server-intro#jboss) | [JBossEAP(6 ~ 8)](/docs/server-intro#undertow)| [WildFly(9 ~ 30)](/docs/server-intro#wildfly) | [Undertow](/docs/server-intro#undertow)|
|
| [JBossAS(4 ~ 7)](/docs/server-intro#jboss) | [JBossEAP(6 ~ 8)](/docs/server-intro#undertow) | [WildFly(9 ~ 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) | | |
|
||||||
|
|
||||||
| [WebSphere(7 ~ 9)](/docs/server-intro#websphere)| [WebLogic (10.3.6 ~ 14)](/docs/server-intro#weblogic) |
|
| [WebSphere(7 ~ 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)| [TongWeb(6 ~ 8)](/docs/server-intro#tongweb) | [InforSuite AS (9 ~ 10)](/docs/server-intro#inforsuite) |
|
| [BES (9.5.x)](/docs/server-intro#bes) | [TongWeb(6 ~ 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 |
|
||||||
|
|
||||||
### 内存马功能
|
### 内存马功能
|
||||||
|
|
||||||
|
|||||||
@@ -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,10 +46,11 @@ 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` 下载当前项目到本地
|
||||||
|
|||||||
@@ -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 被用于各类反序列化字节码工具中,因此使用最为广泛,以下字节码缩小代码被广为流传
|
||||||
|
|||||||
@@ -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。
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+12
-17
@@ -4,30 +4,25 @@ icon: BrainCircuit
|
|||||||
description: 适合集成到已有工具中,实现内存马 payload 的生成,支持 JDK8 以上版本,v1.7.0 开始支持
|
description: 适合集成到已有工具中,实现内存马 payload 的生成,支持 JDK8 以上版本,v1.7.0 开始支持
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
|
||||||
|
|
||||||
> 具体代码可参考 [examples](https://github.com/ReaJason/MemShellParty/tree/master/examples)
|
> 具体代码可参考 [examples](https://github.com/ReaJason/MemShellParty/tree/master/examples)
|
||||||
|
|
||||||
### 添加依赖
|
### 添加依赖
|
||||||
|
|
||||||
<Tabs items={['Maven', 'Gradle']}>
|
<Tabs items={["Maven", "Gradle"]}>
|
||||||
<Tab>
|
<Tab>
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.github.reajason</groupId>
|
<groupId>io.github.reajason</groupId>
|
||||||
<artifactId>generator</artifactId>
|
<artifactId>generator</artifactId>
|
||||||
<version>2.2.0</version>
|
<version>2.2.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab>
|
<Tab>```groovy implementation 'io.github.reajason:generator:2.2.0' ```</Tab>
|
||||||
```groovy
|
|
||||||
implementation 'io.github.reajason:generator:2.2.0'
|
|
||||||
```
|
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|
||||||
### 生成 Tomcat Godzilla Filter 内存马示例
|
### 生成 Tomcat Godzilla Filter 内存马示例
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description: 部署你专有的 MemShellParty
|
|||||||
icon: Rocket
|
icon: Rocket
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
|
||||||
|
|
||||||
## Docker 部署
|
## Docker 部署
|
||||||
|
|
||||||
@@ -12,21 +12,20 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
|||||||
|
|
||||||
使用 docker 部署之后,使用浏览器访问:http://127.0.0.1:8080
|
使用 docker 部署之后,使用浏览器访问:http://127.0.0.1:8080
|
||||||
|
|
||||||
<Tabs items={['Docker Hub 源', 'Github Container Registry 源', '南大 Github Container Registry 镜像源']}>
|
<Tabs
|
||||||
|
items={["Docker Hub 源", "Github Container Registry 源", "南大 Github Container Registry 镜像源"]}
|
||||||
|
>
|
||||||
<Tab>
|
<Tab>
|
||||||
```bash
|
```bash docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party
|
||||||
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party reajason/memshell-party:latest
|
reajason/memshell-party:latest ```
|
||||||
```
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab>
|
<Tab>
|
||||||
```bash
|
```bash docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party
|
||||||
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.io/reajason/memshell-party:latest
|
ghcr.io/reajason/memshell-party:latest ```
|
||||||
```
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab>
|
<Tab>
|
||||||
```bash
|
```bash docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party
|
||||||
docker run --pull=always --rm -it -d -p 8080:8080 --name memshell-party ghcr.nju.edu.cn/reajason/memshell-party:latest
|
ghcr.nju.edu.cn/reajason/memshell-party:latest ```
|
||||||
```
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|||||||
@@ -26,19 +26,19 @@ Tomcat 使用的是自己 Catalina 模块提供的 Servlets 实现,限制较
|
|||||||
<div className="tomcat-version-table">
|
<div className="tomcat-version-table">
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Servlet | JakartaServlet | Filter | JakartaFilter | Listener | JakartaListener | Valve | JakartaValve | ProxyValve | JakartaProxyValve | WebSocket | JakartaWebSocket | Upgrade | AgentFilterChain | AgentContextValve |
|
| Version | Servlet | JakartaServlet | Filter | JakartaFilter | Listener | JakartaListener | Valve | JakartaValve | ProxyValve | JakartaProxyValve | WebSocket | JakartaWebSocket | Upgrade | AgentFilterChain | AgentContextValve |
|
||||||
|-----|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
| --------------- | :-----: | :------------: | :----: | :-----------: | :------: | :-------------: | :---: | :----------: | :--------: | :---------------: | :-------: | :--------------: | :-----: | :--------------: | :---------------: |
|
||||||
| Tomcat 5.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | | | ✔️ | ✔️ |
|
| Tomcat 5.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | | | ✔️ | ✔️ |
|
||||||
| Tomcat 6.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | | | ✔️ | ✔️ |
|
| Tomcat 6.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | | | ✔️ | ✔️ |
|
||||||
| Tomcat 7.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ | ✔️ |
|
| Tomcat 7.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ | ✔️ |
|
||||||
| Tomcat 8.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
| Tomcat 8.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
||||||
| Tomcat 9.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
| Tomcat 9.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
||||||
| Tomcat 10.x | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ |
|
| Tomcat 10.x | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| Tomcat 11.x | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ |
|
| Tomcat 11.x | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| Spring Boot 1.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
| Spring Boot 1.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
||||||
| Spring Boot 2.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
| Spring Boot 2.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
||||||
| Spring Boot 3.x | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ |
|
| Spring Boot 3.x | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| BES 9.5.1 | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ | ✔️ |
|
| BES 9.5.1 | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 1:Tomcat 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 1:Tomcat 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -66,19 +66,19 @@ Jetty6 版本使用的包名为 `org.mortbay.jetty`,而 7 以上使用的是 `
|
|||||||
<div className="jetty-version-table">
|
<div className="jetty-version-table">
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Servlet | JakartaServlet | Filter | JakartaFilter | Listener | JakartaListener | Handler | JakartaHandler | Customizer | AgentHandler |
|
| Version | Servlet | JakartaServlet | Filter | JakartaFilter | Listener | JakartaListener | Handler | JakartaHandler | Customizer | AgentHandler |
|
||||||
|-----|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
| ----------------------- | :-----: | :------------: | :----: | :-----------: | :------: | :-------------: | :-----: | :------------: | :--------: | :----------: |
|
||||||
| Jetty 6.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ |
|
| Jetty 6.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ |
|
||||||
| Jetty 7.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ |
|
| Jetty 7.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ |
|
||||||
| Jetty 8.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ |
|
| Jetty 8.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ |
|
||||||
| Jetty 9.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| Jetty 9.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
| Jetty 10.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| Jetty 10.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
| Jetty 11.x | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
| Jetty 11.x | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
||||||
| Jetty 12.x ee8 | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ |
|
| Jetty 12.x ee8 | ✔️ | | ✔️ | | ✔️ | | ✔️ | | | ✔️ |
|
||||||
| Jetty 12.x ee9~ee11 | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
| Jetty 12.x ee9~ee11 | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
||||||
| Spring Boot 1.x/2.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| Spring Boot 1.x/2.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
| Spring Boot 3.0.x/3.1.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| Spring Boot 3.0.x/3.1.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
| Spring Boot 3.2.x+ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
| Spring Boot 3.2.x+ | | ✔️ | | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 2:Jetty 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 2:Jetty 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -106,11 +106,11 @@ JBoss 分为 JBossAS 和 JBossEAP,JBossAS 全版本和 JBossEAP6 使用的 Cat
|
|||||||
<div className="jboss-version-table">
|
<div className="jboss-version-table">
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Filter | Listener | Valve | ProxyValve | AgentFilterChain | AgentContextValve |
|
| Version | Filter | Listener | Valve | ProxyValve | AgentFilterChain | AgentContextValve |
|
||||||
|---|:---:|:---:|:---:|:---:|:---:|:---:|
|
| ------------------ | :----: | :------: | :---: | :--------: | :--------------: | :---------------: |
|
||||||
| JBoss 4.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| JBoss 4.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| JBoss AS 5.x ~ 7.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| JBoss AS 5.x ~ 7.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| JBoss EAP 6.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| JBoss EAP 6.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 3:JBoss 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 3:JBoss 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -138,14 +138,14 @@ Undertow 是 JBoss 社区开发的高性能 Java Web 服务器,Wildfly 以及
|
|||||||
<div className="undertow-version-table">
|
<div className="undertow-version-table">
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Servlet | JakartaServlet | Filter | JakartaFilter | Listener | JakartaListener | AgentServletHandler |
|
| Version | Servlet | JakartaServlet | Filter | JakartaFilter | Listener | JakartaListener | AgentServletHandler |
|
||||||
|-----|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
| -------------------- | :-----: | :------------: | :----: | :-----------: | :------: | :-------------: | :-----------------: |
|
||||||
| Undertow 1.x ~ 2.2.x | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
| Undertow 1.x ~ 2.2.x | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
||||||
| Undertow 2.3.x+ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| Undertow 2.3.x+ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
| JBoss EAP 7.x | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
| JBoss EAP 7.x | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
||||||
| JBoss EAP 8.x | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| JBoss EAP 8.x | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
| WildFly 9.x ~ 26.x | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
| WildFly 9.x ~ 26.x | ✔️ | | ✔️ | | ✔️ | | ✔️ |
|
||||||
| WildFly 27.x+ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| WildFly 27.x+ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 4:Undertow 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 4:Undertow 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -168,14 +168,14 @@ GlassFish 进行内存马的生成。
|
|||||||
<div className="glassfish-version-table">
|
<div className="glassfish-version-table">
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Filter | JakartaFilter | Listener | JakartaListener | Valve | JakartaValve | AgentFilterChain | AgentContextValve |
|
| Version | Filter | JakartaFilter | Listener | JakartaListener | Valve | JakartaValve | AgentFilterChain | AgentContextValve |
|
||||||
|-----|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
| ------------------- | :----: | :-----------: | :------: | :-------------: | :---: | :----------: | :--------------: | :---------------: |
|
||||||
| GlassFish 4.x ~ 5.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| GlassFish 4.x ~ 5.x | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
| GlassFish 6.x+ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
| GlassFish 6.x+ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
||||||
| Payara 5.x+ | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| Payara 5.x+ | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
| Payara 6.x+ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
| Payara 6.x+ | | ✔️ | | ✔️ | | ✔️ | ✔️ | ✔️ |
|
||||||
| Apusic V10 | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| Apusic V10 | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
| Primeton V6.5 | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
| Primeton V6.5 | ✔️ | | ✔️ | | ✔️ | | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 5:GlassFish 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 5:GlassFish 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -202,10 +202,10 @@ Resin 使用的包名为 `com.caucho.`,泛微 OA 使用的就是 Resin,服
|
|||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Filter | Listener | AgentFilterChain |
|
| Version | Filter | Listener | AgentFilterChain |
|
||||||
|-----|:---:|:---:|:---:|
|
| --------- | :----: | :------: | :--------------: |
|
||||||
| Resin 3.x | ✔️ | ✔️ | ✔️ |
|
| Resin 3.x | ✔️ | ✔️ | ✔️ |
|
||||||
| Resin 4.x | ✔️ | ✔️ | ✔️ |
|
| Resin 4.x | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 6:Resin 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 6:Resin 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -218,11 +218,11 @@ WebLogic 使用的包名为 `weblogic.`,服务类型选择 WebLogic 进行内
|
|||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Filter | Listener | AgentServletContext |
|
| Version | Filter | Listener | AgentServletContext |
|
||||||
|-----|:---:|:---:|:---:|
|
| --------------- | :----: | :------: | :-----------------: |
|
||||||
| WebLogic 10.3.6 | ✔️ | ✔️ | ✔️ |
|
| WebLogic 10.3.6 | ✔️ | ✔️ | ✔️ |
|
||||||
| WebLogic 12.x | ✔️ | ✔️ | ✔️ |
|
| WebLogic 12.x | ✔️ | ✔️ | ✔️ |
|
||||||
| WebLogic 14.x | ✔️ | ✔️ | ✔️ |
|
| WebLogic 14.x | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 7:WebLogic 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 7:WebLogic 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -236,11 +236,11 @@ WebSphere 是 IBM 研发的商用 Servlets 容器,开源版本为 Websphere li
|
|||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Filter | Listener | AgentFilterManager |
|
| Version | Filter | Listener | AgentFilterManager |
|
||||||
|-----|:---:|:---:|:---:|
|
| ------------- | :----: | :------: | :------------------------------------: |
|
||||||
| WebSphere 7.x | ✔️ | ✔️ | ✔️(需要开启 debug 模式才允许 attach) |
|
| WebSphere 7.x | ✔️ | ✔️ | ✔️(需要开启 debug 模式才允许 attach) |
|
||||||
| WebSphere 8.x | ✔️ | ✔️ | ✔️ |
|
| WebSphere 8.x | ✔️ | ✔️ | ✔️ |
|
||||||
| WebSphere 9.x | ✔️ | ✔️ | ✔️ |
|
| WebSphere 9.x | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 8:WebSphere 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 8:WebSphere 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -255,10 +255,10 @@ BES 进行内存马的生成。
|
|||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Filter | Listener | Valve | AgentFilterChain | AgentContextValve |
|
| Version | Filter | Listener | Valve | AgentFilterChain | AgentContextValve |
|
||||||
|---|:---:|:---:|:---:|:---:|:---:|
|
| --------- | :----: | :------: | :---: | :--------------: | :---------------: |
|
||||||
| BES 9.5.2 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| BES 9.5.2 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| BES 9.5.5 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| BES 9.5.5 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 9:BES 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 9:BES 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -277,11 +277,11 @@ TongWeb 东方通,其基于 Tomcat 进行二开,并且在最初的 6 版本
|
|||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Filter | Listener | Valve | AgentFilterChain | AgentContextValve |
|
| Version | Filter | Listener | Valve | AgentFilterChain | AgentContextValve |
|
||||||
|---|:---:|:---:|:---:|:---:|:---:|
|
| ----------- | :----: | :------: | :---: | :--------------: | :---------------: |
|
||||||
| TongWeb 6.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| TongWeb 6.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| TongWeb 7.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| TongWeb 7.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| TongWeb 8.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| TongWeb 8.x | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 10:TongWeb 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 10:TongWeb 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -296,10 +296,10 @@ Apusic 进行内存马生成,Apusic10 版本选择 [GlassFish](#glassfish) 进
|
|||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Filter | Listener | AgentFilterChain |
|
| Version | Filter | Listener | AgentFilterChain |
|
||||||
|---|:---:|:---:|:---:|
|
| ------------ | :----: | :------: | :--------------: |
|
||||||
| Apusic 9.0 | ✔️ | ✔️ | ✔️ |
|
| Apusic 9.0 | ✔️ | ✔️ | ✔️ |
|
||||||
| Apusic 9.0.1 | ✔️ | ✔️ | ✔️ |
|
| Apusic 9.0.1 | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 11:Apusic 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 11:Apusic 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -319,10 +319,10 @@ iasFilterConfigs 因此 Filter 注入单独进行了适配。服务类型选择
|
|||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Filter | Listener | Valve | AgentFilterChain | AgentContextValve |
|
| Version | Filter | Listener | Valve | AgentFilterChain | AgentContextValve |
|
||||||
|---|:---:|:---:|:---:|:---:|:---:|
|
| ------------- | :----: | :------: | :---: | :--------------: | :---------------: |
|
||||||
| InforSuite 9| ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| InforSuite 9 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
| InforSuite 10 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
| InforSuite 10 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 12:InforSuite 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 12:InforSuite 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -336,12 +336,12 @@ SpringWebMVC 进行内存马生成。
|
|||||||
<div className="springwebmvc-version-table">
|
<div className="springwebmvc-version-table">
|
||||||
<figure>
|
<figure>
|
||||||
|
|
||||||
| Version | Interceptor | JakartaInterceptor | ControllerHandler | JakartaControllerHandler | AgentFrameworkServlet |
|
| Version | Interceptor | JakartaInterceptor | ControllerHandler | JakartaControllerHandler | AgentFrameworkServlet |
|
||||||
|---|:---:|:---:|:---:|:---:|:---:|
|
| ---------------------- | :---------: | :----------------: | :---------------: | :----------------------: | :-------------------: |
|
||||||
| SpringWebMVC 3.x ~ 5.x| ✔️ | | ✔️ | | ✔️ |
|
| SpringWebMVC 3.x ~ 5.x | ✔️ | | ✔️ | | ✔️ |
|
||||||
| Spring Boot 1.x/2.x | ✔️ | | ✔️ | | ✔️ |
|
| Spring Boot 1.x/2.x | ✔️ | | ✔️ | | ✔️ |
|
||||||
| SpringWebMVC 6.x | | ✔️ | | ✔️ | ✔️ |
|
| SpringWebMVC 6.x | | ✔️ | | ✔️ | ✔️ |
|
||||||
| Spring Boot 3.x | | ✔️ | | ✔️ | ✔️ |
|
| Spring Boot 3.x | | ✔️ | | ✔️ | ✔️ |
|
||||||
|
|
||||||
<figcaption align="center">表 13:SpringWebMVC 各版本内存马挂载类型参考表</figcaption>
|
<figcaption align="center">表 13:SpringWebMVC 各版本内存马挂载类型参考表</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
@@ -359,7 +359,6 @@ SpringWebMVC 进行内存马生成。
|
|||||||
Spring Boot 项目中基于 reactor 异步 IO 模型的服务组件,底层使用的 Netty,一般常见于各种 SpringCloud 项目,例如网关。服务类型选择
|
Spring Boot 项目中基于 reactor 异步 IO 模型的服务组件,底层使用的 Netty,一般常见于各种 SpringCloud 项目,例如网关。服务类型选择
|
||||||
SpringWebFlux 进行内存马生成。
|
SpringWebFlux 进行内存马生成。
|
||||||
|
|
||||||
|
|
||||||
## XXL-JOB
|
## XXL-JOB
|
||||||
|
|
||||||
> 官网:https://www.xuxueli.com/xxl-job/
|
> 官网:https://www.xuxueli.com/xxl-job/
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public interface Filter {
|
|||||||
|
|
||||||
shell 的目的,就是为了定义一个入口,我们能与 Web 服务器进行交互。在 Filter 中我们就是实现 doFilter 来满足需求,以下定义了一个命令回显的 FilterShell。
|
shell 的目的,就是为了定义一个入口,我们能与 Web 服务器进行交互。在 Filter 中我们就是实现 doFilter 来满足需求,以下定义了一个命令回显的 FilterShell。
|
||||||
|
|
||||||
1. 一般而言,我们会为 FilterShell 注册 url-pattern 为 `/*`,这样无论访问哪个路径都能被调用到,而且为了绕过登录过滤器,我们会把 FilterShell 注册为 FilterChain 中的第一个过滤器。
|
1. 一般而言,我们会为 FilterShell 注册 url-pattern 为 `/*`,这样无论访问哪个路径都能被调用到,而且为了绕过登录过滤器,我们会把 FilterShell 注册为 FilterChain 中的第一个过滤器。
|
||||||
2. 交互的入口是 `request.getParameter` 支持两种方式传参。GET/POST 请求发送 `/?paramName=whoami`,也可以发送 POST 请求时使用 `application/x-www-form-urlencoded` 发送 body 参数。`multipart/form-data` 是不支持从 `request.getParameter` 获取参数的。
|
2. 交互的入口是 `request.getParameter` 支持两种方式传参。GET/POST 请求发送 `/?paramName=whoami`,也可以发送 POST 请求时使用 `application/x-www-form-urlencoded` 发送 body 参数。`multipart/form-data` 是不支持从 `request.getParameter` 获取参数的。
|
||||||
3. 当 Filter 注册的 url-pattern 为 `/*` 时,我们拿到 cmd 参数,就可以执行命令并填充响应对象 `return` 结束请求,而在拿不到参数的时候就必须调用 `chain.doFilter(servletRequest, servletResponse)`,否则正常的业务就不会被执行。
|
3. 当 Filter 注册的 url-pattern 为 `/*` 时,我们拿到 cmd 参数,就可以执行命令并填充响应对象 `return` 结束请求,而在拿不到参数的时候就必须调用 `chain.doFilter(servletRequest, servletResponse)`,否则正常的业务就不会被执行。
|
||||||
|
|
||||||
|
|||||||
@@ -11,18 +11,15 @@ title: Java Servlet 规范内存马
|
|||||||
## Servlet 三大 Web 组件
|
## Servlet 三大 Web 组件
|
||||||
|
|
||||||
<Cards>
|
<Cards>
|
||||||
<Card
|
<Card href="/docs/servlet/servlet" title="Servlet 内存马">
|
||||||
href="/docs/servlet/servlet"
|
创建独特的 entrypoint 访问路径
|
||||||
title="Servlet 内存马"
|
</Card>
|
||||||
>创建独特的 entrypoint 访问路径</Card>
|
<Card href="/docs/servlet/filter" title="Filter 内存马">
|
||||||
<Card
|
过滤器,遇事不决写点特别的东西然后中断请求
|
||||||
href="/docs/servlet/filter"
|
</Card>
|
||||||
title="Filter 内存马"
|
<Card href="/docs/servlet/listener" title="Listener 内存马">
|
||||||
>过滤器,遇事不决写点特别的东西然后中断请求</Card>
|
监听器,请求创建时触发,拿请求体和响应体做不该做的事情
|
||||||
<Card
|
</Card>
|
||||||
href="/docs/servlet/listener"
|
|
||||||
title="Listener 内存马"
|
|
||||||
>监听器,请求创建时触发,拿请求体和响应体做不该做的事情</Card>
|
|
||||||
</Cards>
|
</Cards>
|
||||||
|
|
||||||
## ServletContext
|
## ServletContext
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ title: Listener
|
|||||||
|
|
||||||
Servlet 事件监听器支持当 ServletContext、HttpSession 和 ServletRequest 状态变更时发送事件通知。每个事件类型的监听器都支持多个,并且开发者可以指定监听器的调用顺序。
|
Servlet 事件监听器支持当 ServletContext、HttpSession 和 ServletRequest 状态变更时发送事件通知。每个事件类型的监听器都支持多个,并且开发者可以指定监听器的调用顺序。
|
||||||
|
|
||||||
| Listener 接口类 | 描述 |
|
| Listener 接口类 | 描述 |
|
||||||
|--------------------------------------------------|---------------------------------|
|
| ------------------------------------------------ | ------------------------------------------ |
|
||||||
| javax.servlet.ServletContextListener | 在 ServletContext 创建以及销毁时 |
|
| javax.servlet.ServletContextListener | 在 ServletContext 创建以及销毁时 |
|
||||||
| javax.servlet.ServletContextAttributeListener | 在 ServletContext 添加、移除或替换属性时 |
|
| javax.servlet.ServletContextAttributeListener | 在 ServletContext 添加、移除或替换属性时 |
|
||||||
| javax.servlet.http.HttpSessionListener | 在 HttpSession 创建和销毁时 |
|
| javax.servlet.http.HttpSessionListener | 在 HttpSession 创建和销毁时 |
|
||||||
| javax.servlet.http.HttpSessionAttributeListener | 在 HttpSession 上添加、移除或替换属性 |
|
| javax.servlet.http.HttpSessionAttributeListener | 在 HttpSession 上添加、移除或替换属性 |
|
||||||
| javax.servlet.http.HttpSessionIdListener | 在 HttpSession id 变化时 |
|
| javax.servlet.http.HttpSessionIdListener | 在 HttpSession id 变化时 |
|
||||||
| javax.servlet.http.HttpSessionActivationListener | 在 HttpSession 激活或钝化时 |
|
| javax.servlet.http.HttpSessionActivationListener | 在 HttpSession 激活或钝化时 |
|
||||||
| javax.servlet.http.HttpSessionBindingListener | 在 HttpSession 上对象绑定或解绑时 |
|
| javax.servlet.http.HttpSessionBindingListener | 在 HttpSession 上对象绑定或解绑时 |
|
||||||
| javax.servlet.ServletRequestListener | 在 ServletRequest 在将要被 Web 容器处理时 |
|
| javax.servlet.ServletRequestListener | 在 ServletRequest 在将要被 Web 容器处理时 |
|
||||||
| javax.servlet.ServletRequestAttributeListener | 在 ServletRequest 上添加、移除或替换属性时 |
|
| javax.servlet.ServletRequestAttributeListener | 在 ServletRequest 上添加、移除或替换属性时 |
|
||||||
| javax.servlet.AsyncListener | 在异步操作开始、超时或完成时 |
|
| javax.servlet.AsyncListener | 在异步操作开始、超时或完成时 |
|
||||||
|
|
||||||
## ServletRequestListener
|
## ServletRequestListener
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
{
|
{
|
||||||
"title": "Java Servlet 规范内存马",
|
"title": "Java Servlet 规范内存马",
|
||||||
"pages": [
|
"pages": ["servlet", "filter", "listener", "---Servlet 容器---", "...(container)"]
|
||||||
"servlet",
|
|
||||||
"filter",
|
|
||||||
"listener",
|
|
||||||
"---Servlet 容器---",
|
|
||||||
"...(container)"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
title: Command
|
title: Command
|
||||||
description: 可以让我们执行任意非交互式命令并回显命令结果的内存马。
|
description: 可以让我们执行任意非交互式命令并回显命令结果的内存马。
|
||||||
---
|
---
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|
||||||
|
import { Step, Steps } from "fumadocs-ui/components/steps";
|
||||||
|
|
||||||
## WebSocket 内存马
|
## WebSocket 内存马
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|||||||
### 选择 Command 并填写参数
|
### 选择 Command 并填写参数
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step>
|
<Step>
|
||||||
@@ -34,6 +36,7 @@ Connected (press CTRL+C to quit)
|
|||||||
|
|
||||||
>
|
>
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
@@ -44,6 +47,7 @@ Connected (press CTRL+C to quit)
|
|||||||
### 选择 Command 并填写参数
|
### 选择 Command 并填写参数
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step>
|
<Step>
|
||||||
@@ -77,5 +81,6 @@ Connected (press CTRL+C to quit)
|
|||||||
|
|
||||||
>
|
>
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -3,7 +3,7 @@ title: Godzilla
|
|||||||
description: Godzilla 是继 Behinder 之后的又一加密 webshell 巨作,其提供的丰富的内置插件和高度解耦的插件模块化系统使得 Godzilla 二开遍地开花。
|
description: Godzilla 是继 Behinder 之后的又一加密 webshell 巨作,其提供的丰富的内置插件和高度解耦的插件模块化系统使得 Godzilla 二开遍地开花。
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
import { Step, Steps } from "fumadocs-ui/components/steps";
|
||||||
|
|
||||||
<Callout type="info">项目地址:https://github.com/BeichenDream/Godzilla</Callout>
|
<Callout type="info">项目地址:https://github.com/BeichenDream/Godzilla</Callout>
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|||||||
由于 Godzilla 的超强兼容性,使得其能修改服务端代码适配各种各样的环境,因此 Godzilla 是默认首选的内存马工具。
|
由于 Godzilla 的超强兼容性,使得其能修改服务端代码适配各种各样的环境,因此 Godzilla 是默认首选的内存马工具。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step>
|
<Step>
|
||||||
@@ -27,6 +28,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|||||||
2. 为了防止正常业务进入 Godzilla 内存马影响到正常业务,需要指定流量特征进入 Godzilla 内存马逻辑处理,此处使用特定请求头和请求值来标识,默认情况下是 User-Agent,请求值会随机生成
|
2. 为了防止正常业务进入 Godzilla 内存马影响到正常业务,需要指定流量特征进入 Godzilla 内存马逻辑处理,此处使用特定请求头和请求值来标识,默认情况下是 User-Agent,请求值会随机生成
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step>
|
<Step>
|
||||||
@@ -43,6 +45,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|||||||
3. 点击测试连接尝试连接,连接成功即可点击添加到 shell 管理界面
|
3. 点击测试连接尝试连接,连接成功即可点击添加到 shell 管理界面
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
@@ -55,6 +58,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|||||||
目前仅支持 Tomcat,当选中 Tomcat Godzilla,可以在内存马挂载类型处选择 WebSocket 内存马
|
目前仅支持 Tomcat,当选中 Tomcat Godzilla,可以在内存马挂载类型处选择 WebSocket 内存马
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step>
|
<Step>
|
||||||
@@ -78,6 +82,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|||||||
如果你是用是哥斯拉特战版,需要注意请求地址不要写 path 只写 ip:host,密码处填 path 即 `/app/g-ws`
|
如果你是用是哥斯拉特战版,需要注意请求地址不要写 path 只写 ip:host,密码处填 path 即 `/app/g-ws`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -1,12 +1,4 @@
|
|||||||
{
|
{
|
||||||
"title": "内存马工具",
|
"title": "内存马工具",
|
||||||
"pages": [
|
"pages": ["godzilla", "suo5", "behinder", "command", "antsword", "neoregeorg", "proxy"]
|
||||||
"godzilla",
|
|
||||||
"suo5",
|
|
||||||
"behinder",
|
|
||||||
"command",
|
|
||||||
"antsword",
|
|
||||||
"neoregeorg",
|
|
||||||
"proxy"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
title: Proxy
|
title: Proxy
|
||||||
---
|
---
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|
||||||
|
import { Step, Steps } from "fumadocs-ui/components/steps";
|
||||||
|
|
||||||
参考地址:https://github.com/veo/wsMemShell/blob/main/static/websocketproxy.md
|
参考地址:https://github.com/veo/wsMemShell/blob/main/static/websocketproxy.md
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps';
|
|||||||
### 选择 Proxy
|
### 选择 Proxy
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step>
|
<Step>
|
||||||
@@ -35,6 +37,7 @@ https://github.com/go-gost/gost
|
|||||||
```bash
|
```bash
|
||||||
> curl -x socks5h://127.0.0.1:1080 https://www.baidu.com
|
> curl -x socks5h://127.0.0.1:1080 https://www.baidu.com
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
@@ -45,6 +48,7 @@ https://github.com/go-gost/gost
|
|||||||
### 选择 Proxy 并填写参数
|
### 选择 Proxy 并填写参数
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step>
|
<Step>
|
||||||
@@ -97,5 +101,6 @@ chains:
|
|||||||
```bash
|
```bash
|
||||||
> curl -x socks5h://127.0.0.1:1080 https://www.baidu.com
|
> curl -x socks5h://127.0.0.1:1080 https://www.baidu.com
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -3,28 +3,28 @@ title: Suo5
|
|||||||
description: Suo5 是一款久经实战检验的高性能 HTTP 正向代理工具,持续打磨只为解决一个需求:不出网场景下的稳定正向代理。
|
description: Suo5 是一款久经实战检验的高性能 HTTP 正向代理工具,持续打磨只为解决一个需求:不出网场景下的稳定正向代理。
|
||||||
---
|
---
|
||||||
|
|
||||||
import { Step, Steps } from 'fumadocs-ui/components/steps';
|
import { Step, Steps } from "fumadocs-ui/components/steps";
|
||||||
|
|
||||||
<Callout type="info">项目地址:https://github.com/zema1/suo5</Callout>
|
<Callout type="info">项目地址:https://github.com/zema1/suo5</Callout>
|
||||||
<Callout type="success">原理介绍:[https://koalr.me/posts/suo5-a-hign-performace-http-socks/](https://web.archive.org/web/20250430113018/https://koalr.me/posts/suo5-a-hign-performace-http-socks/)</Callout>
|
<Callout type="success">
|
||||||
|
原理介绍:[https://koalr.me/posts/suo5-a-hign-performace-http-socks/](https://web.archive.org/web/20250430113018/https://koalr.me/posts/suo5-a-hign-performace-http-socks/)
|
||||||
|
</Callout>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 主要特性
|
## 主要特性
|
||||||
|
|
||||||
- **优异的传输性能**
|
- **优异的传输性能**
|
||||||
- 全双工:借助双向 Chunked-Encoding 实现单连接双向通信,传输性能接近 FRP
|
- 全双工:借助双向 Chunked-Encoding 实现单连接双向通信,传输性能接近 FRP
|
||||||
- 半双工:下行长连接 + 上行短连接实现,在 Nginx 反代场景下仍可保持良好性能
|
- 半双工:下行长连接 + 上行短连接实现,在 Nginx 反代场景下仍可保持良好性能
|
||||||
- 短链接:上下行均使用短连接,适配多层反代和严格限制长连接的场景,作为兜底方案
|
- 短链接:上下行均使用短连接,适配多层反代和严格限制长连接的场景,作为兜底方案
|
||||||
- **复杂网络环境支持**
|
- **复杂网络环境支持**
|
||||||
- 支持一层、两层、多层反向代理下的稳定连接
|
- 支持一层、两层、多层反向代理下的稳定连接
|
||||||
- 支持通过流量转发和请求重试来支持负载均衡场景
|
- 支持通过流量转发和请求重试来支持负载均衡场景
|
||||||
- 支持配置上游代理(HTTP/SOCKS5)
|
- 支持配置上游代理(HTTP/SOCKS5)
|
||||||
- **稳定可靠的工程实现**
|
- **稳定可靠的工程实现**
|
||||||
- 正确可靠的连接控制、并发管理、心跳保活、异常重连等
|
- 正确可靠的连接控制、并发管理、心跳保活、异常重连等
|
||||||
- 完善的单元测试和集成测试保障质量
|
- 完善的单元测试和集成测试保障质量
|
||||||
|
|
||||||
## 使用步骤
|
## 使用步骤
|
||||||
|
|
||||||
@@ -57,6 +57,7 @@ Suo5 目前有 [1.x](https://github.com/zema1/suo5/releases/tag/v1.3.1) 版本
|
|||||||
在 [zema1/suo5/releases](https://github.com/zema1/suo5/releases) 下载合适的 suo5 客户端
|
在 [zema1/suo5/releases](https://github.com/zema1/suo5/releases) 下载合适的 suo5 客户端
|
||||||
|
|
||||||
1. 如果请求头使用默认的 User-Agent,则直接通过 `--ua "xXksjas"` 来指定即可,假设生成的为 `User-Agent: xXksjas`,则通过以下命令进行连接
|
1. 如果请求头使用默认的 User-Agent,则直接通过 `--ua "xXksjas"` 来指定即可,假设生成的为 `User-Agent: xXksjas`,则通过以下命令进行连接
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ./suo5 -t http://target.com/suo5.jsp --ua "xXksjas"
|
$ ./suo5 -t http://target.com/suo5.jsp --ua "xXksjas"
|
||||||
...
|
...
|
||||||
@@ -71,7 +72,9 @@ $ ./suo5 -t http://target.com/suo5.jsp --ua "xXksjas"
|
|||||||
[INFO] 14:28 connection closed, 127.0.0.1:0
|
[INFO] 14:28 connection closed, 127.0.0.1:0
|
||||||
[INFO] 14:28 congratulations! everything works fine
|
[INFO] 14:28 congratulations! everything works fine
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 如果请求头使用其他,例如 Referer,则需要使用 `-H "Referer: xXksjas"` 来连接,例如
|
2. 如果请求头使用其他,例如 Referer,则需要使用 `-H "Referer: xXksjas"` 来连接,例如
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ./suo5 -t http://target.com/suo5.jsp -H "Referer: xXksjas"
|
$ ./suo5 -t http://target.com/suo5.jsp -H "Referer: xXksjas"
|
||||||
...
|
...
|
||||||
@@ -88,5 +91,5 @@ $ ./suo5 -t http://target.com/suo5.jsp -H "Referer: xXksjas"
|
|||||||
```
|
```
|
||||||
|
|
||||||
3. 后续的用法可参考官方仓库给出的 [Suo5 使用指南](https://github.com/zema1/suo5#%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
|
3. 后续的用法可参考官方仓库给出的 [Suo5 使用指南](https://github.com/zema1/suo5#%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -29,4 +29,3 @@ HandlerMapping 里面决定了我们 Handler 应该如何注册进去的核心
|
|||||||
2. **org.springframework.web.servlet.function.support.HandlerFunctionAdapter**:需要 handler 继承 org.springframework.web.servlet.function.HandlerFunction。
|
2. **org.springframework.web.servlet.function.support.HandlerFunctionAdapter**:需要 handler 继承 org.springframework.web.servlet.function.HandlerFunction。
|
||||||
3. **org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter**:需要 handler 继承 org.springframework.web.HttpRequestHandler。
|
3. **org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter**:需要 handler 继承 org.springframework.web.HttpRequestHandler。
|
||||||
4. **org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter**:需要 handler 继承 org.springframework.web.servlet.mvc.Controller。
|
4. **org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter**:需要 handler 继承 org.springframework.web.servlet.mvc.Controller。
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ ApplicationContext 是 BeanFactory 的子接口。在漏洞注入中我们常用
|
|||||||
Spirng WebMVC 对 Java Servlet 规范进行封装实现 MVC 架构,可以部署在任意实现 Java Servlet 规范的容器上。
|
Spirng WebMVC 对 Java Servlet 规范进行封装实现 MVC 架构,可以部署在任意实现 Java Servlet 规范的容器上。
|
||||||
|
|
||||||
通过 org.springframework.web.servlet.DispatcherServlet 配置 `/*` 的 urlPattern,实现接管业务访问入口,让所有流量进入 Spring WebMVC 框架。其中最重要的方法是 `doDispatcher`,可以从中窥视 Spring WebMVC 处理请求的主流程。
|
通过 org.springframework.web.servlet.DispatcherServlet 配置 `/*` 的 urlPattern,实现接管业务访问入口,让所有流量进入 Spring WebMVC 框架。其中最重要的方法是 `doDispatcher`,可以从中窥视 Spring WebMVC 处理请求的主流程。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||||
HttpServletRequest processedRequest = request;
|
HttpServletRequest processedRequest = request;
|
||||||
@@ -159,18 +160,14 @@ protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Ex
|
|||||||
我们最常注入的就是 Controller 内存马和 Interceptor 内存马。参考 [SpringWeb 内存马变型 - 银针安全](https://mp.weixin.qq.com/s/aWTcq3690F3CmGGo4A-a0w) 也可以实现其他内存马。
|
我们最常注入的就是 Controller 内存马和 Interceptor 内存马。参考 [SpringWeb 内存马变型 - 银针安全](https://mp.weixin.qq.com/s/aWTcq3690F3CmGGo4A-a0w) 也可以实现其他内存马。
|
||||||
|
|
||||||
<Cards>
|
<Cards>
|
||||||
<Card
|
<Card href="/docs/springweb/controller" title="Controller 内存马">
|
||||||
href="/docs/springweb/controller"
|
控制器,创建独特的 entrypoint 访问路径
|
||||||
title="Controller 内存马"
|
</Card>
|
||||||
>控制器,创建独特的 entrypoint 访问路径</Card>
|
<Card href="/docs/springweb/interceptor" title="Interceptor 内存马">
|
||||||
<Card
|
拦截器,命中 webshell 逻辑中断请求
|
||||||
href="/docs/springweb/interceptor"
|
</Card>
|
||||||
title="Interceptor 内存马"
|
|
||||||
>拦截器,命中 webshell 逻辑中断请求</Card>
|
|
||||||
</Cards>
|
</Cards>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Spring WebFlux
|
### Spring WebFlux
|
||||||
|
|
||||||
> [官方文档 - Spring WebFlux](https://docs.spring.io/spring-framework/reference/web/webflux.html)
|
> [官方文档 - Spring WebFlux](https://docs.spring.io/spring-framework/reference/web/webflux.html)
|
||||||
@@ -203,20 +200,16 @@ WebFlux 中同样可以使用 `@Controller` 和 `@RequestMapping`,因此也有
|
|||||||
当然底层使用的 Netty 提供的 HTTP 服务,因此也有 Netty Handler 马可以打,所以 WebFlux 常见的内存马就是 WebFilter、HandlerMethod、HandlerFunction 和 NettyHandler 马。
|
当然底层使用的 Netty 提供的 HTTP 服务,因此也有 Netty Handler 马可以打,所以 WebFlux 常见的内存马就是 WebFilter、HandlerMethod、HandlerFunction 和 NettyHandler 马。
|
||||||
|
|
||||||
<Cards>
|
<Cards>
|
||||||
<Card
|
<Card href="/docs/springweb/webfilter" title="WebFilter 内存马">
|
||||||
href="/docs/springweb/webfilter"
|
过滤器,总是需要一种方式对所有进入的请求进行处理
|
||||||
title="WebFilter 内存马"
|
</Card>
|
||||||
>过滤器,总是需要一种方式对所有进入的请求进行处理</Card>
|
<Card href="/docs/springweb/handler-method" title="HandlerMethod 内存马">
|
||||||
<Card
|
@RequestMapping,WebMVC 式创建路由的方式
|
||||||
href="/docs/springweb/handler-method"
|
</Card>
|
||||||
title="HandlerMethod 内存马"
|
<Card href="/docs/springweb/handler-function" title="HandlerFunction 内存马">
|
||||||
>@RequestMapping,WebMVC 式创建路由的方式</Card>
|
RouterFunction,函数式路由声明方式
|
||||||
<Card
|
</Card>
|
||||||
href="/docs/springweb/handler-function"
|
<Card href="/docs/springweb/netty-handler" title="Netty Handler 内存马">
|
||||||
title="HandlerFunction 内存马"
|
Netty Handler API 允许处理 request 和 response
|
||||||
>RouterFunction,函数式路由声明方式</Card>
|
</Card>
|
||||||
<Card
|
|
||||||
href="/docs/springweb/netty-handler"
|
|
||||||
title="Netty Handler 内存马"
|
|
||||||
>Netty Handler API 允许处理 request 和 response</Card>
|
|
||||||
</Cards>
|
</Cards>
|
||||||
+2
-2
@@ -44,8 +44,6 @@
|
|||||||
"yup": "^1.7.1"
|
"yup": "^1.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"oxfmt": "^0.46.0",
|
|
||||||
"oxlint": "^1.61.0",
|
|
||||||
"@react-router/dev": "^7.14.2",
|
"@react-router/dev": "^7.14.2",
|
||||||
"@tailwindcss/vite": "^4.2.4",
|
"@tailwindcss/vite": "^4.2.4",
|
||||||
"@types/mdx": "^2.0.13",
|
"@types/mdx": "^2.0.13",
|
||||||
@@ -54,6 +52,8 @@
|
|||||||
"@types/react-copy-to-clipboard": "^5.0.7",
|
"@types/react-copy-to-clipboard": "^5.0.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
|
"oxfmt": "^0.46.0",
|
||||||
|
"oxlint": "^1.61.0",
|
||||||
"react-router-devtools": "^6.2.0",
|
"react-router-devtools": "^6.2.0",
|
||||||
"rimraf": "^6.1.3",
|
"rimraf": "^6.1.3",
|
||||||
"serve": "^14.2.6",
|
"serve": "^14.2.6",
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { Config } from '@react-router/dev/config';
|
import type { Config } from "@react-router/dev/config";
|
||||||
import { glob } from 'node:fs/promises';
|
|
||||||
import { createGetUrl, getSlugs } from 'fumadocs-core/source';
|
import { createGetUrl, getSlugs } from "fumadocs-core/source";
|
||||||
|
import { glob } from "node:fs/promises";
|
||||||
import { env } from "node:process";
|
import { env } from "node:process";
|
||||||
|
|
||||||
const getUrl = createGetUrl('/docs');
|
const getUrl = createGetUrl("/docs");
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
basename: env.VITE_APP_BASE_PATH,
|
basename: env.VITE_APP_BASE_PATH,
|
||||||
@@ -19,9 +20,9 @@ export default {
|
|||||||
if (!excluded.includes(path)) paths.push(path);
|
if (!excluded.includes(path)) paths.push(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const entry of glob('**/*.mdx', { cwd: 'content/docs' })) {
|
for await (const entry of glob("**/*.mdx", { cwd: "content/docs" })) {
|
||||||
const slugs = getSlugs(entry);
|
const slugs = getSlugs(entry);
|
||||||
paths.push(getUrl(slugs), `/llms.mdx/docs/${[...slugs, 'content.md'].join('/')}`);
|
paths.push(getUrl(slugs), `/llms.mdx/docs/${[...slugs, "content.md"].join("/")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
|
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
||||||
|
|
||||||
export const docs = defineDocs({
|
export const docs = defineDocs({
|
||||||
dir: 'content/docs',
|
dir: "content/docs",
|
||||||
docs: {
|
docs: {
|
||||||
postprocess: {
|
postprocess: {
|
||||||
includeProcessedMarkdown: true,
|
includeProcessedMarkdown: true,
|
||||||
|
|||||||
+4
-8
@@ -1,21 +1,17 @@
|
|||||||
import path from "node:path";
|
|
||||||
import { env } from "node:process";
|
|
||||||
import { reactRouter } from "@react-router/dev/vite";
|
import { reactRouter } from "@react-router/dev/vite";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import mdx from "fumadocs-mdx/vite";
|
import mdx from "fumadocs-mdx/vite";
|
||||||
|
import path from "node:path";
|
||||||
|
import { env } from "node:process";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import devtoolsJson from "vite-plugin-devtools-json";
|
import devtoolsJson from "vite-plugin-devtools-json";
|
||||||
|
|
||||||
import * as MdxConfig from "./source.config";
|
import * as MdxConfig from "./source.config";
|
||||||
|
|
||||||
const isDev = env.NODE_ENV === "development";
|
const isDev = env.NODE_ENV === "development";
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: isDev ? "/" : `${env.VITE_APP_API_URL}/`,
|
base: isDev ? "/" : `${env.VITE_APP_API_URL}/`,
|
||||||
plugins: [
|
plugins: [mdx(MdxConfig), tailwindcss(), reactRouter(), devtoolsJson()],
|
||||||
mdx(MdxConfig),
|
|
||||||
tailwindcss(),
|
|
||||||
reactRouter(),
|
|
||||||
devtoolsJson(),
|
|
||||||
],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
tsconfigPaths: true,
|
tsconfigPaths: true,
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user