mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
feat: support boot-ui
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import { Button, ButtonProps } from "@/components/ui/button.tsx";
|
||||
import { cn } from "@/lib/utils.ts";
|
||||
import { CheckIcon, ClipboardIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { HTMLProps, useEffect, useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { materialDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface CopyButtonProps extends ButtonProps {
|
||||
value: string;
|
||||
@@ -14,24 +15,21 @@ export function copyToClipboardWithMeta(value: string) {
|
||||
navigator.clipboard.writeText(value);
|
||||
}
|
||||
|
||||
export function CopyButton({
|
||||
value,
|
||||
className,
|
||||
src,
|
||||
variant = "ghost",
|
||||
...props
|
||||
}: CopyButtonProps) {
|
||||
export function CopyButton({ value, className, src, variant = "ghost", ...props }: CopyButtonProps) {
|
||||
const [hasCopied, setHasCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setHasCopied(false);
|
||||
}, 2000);
|
||||
}, []);
|
||||
if (hasCopied) {
|
||||
setTimeout(() => {
|
||||
setHasCopied(false);
|
||||
}, 1000);
|
||||
}
|
||||
}, [hasCopied]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="icon"
|
||||
type="button"
|
||||
variant={variant}
|
||||
className={cn(
|
||||
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:h-3 [&_svg]:w-3",
|
||||
@@ -40,6 +38,7 @@ export function CopyButton({
|
||||
onClick={() => {
|
||||
copyToClipboardWithMeta(value);
|
||||
setHasCopied(true);
|
||||
toast.success("复制成功");
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
@@ -53,22 +52,33 @@ export function CodeViewer({
|
||||
code,
|
||||
language,
|
||||
showLineNumbers = true,
|
||||
wrapLongLines = false,
|
||||
}: {
|
||||
code: string;
|
||||
language: string;
|
||||
showLineNumbers?: boolean;
|
||||
wrapLongLines?: boolean;
|
||||
}) {
|
||||
const lineProps: lineTagPropsFunction | HTMLProps<HTMLElement> | undefined = wrapLongLines
|
||||
? { style: { overflowWrap: "break-word", whiteSpace: "pre-wrap" } }
|
||||
: undefined;
|
||||
return (
|
||||
<div className="relative overflow-hidden text-xs">
|
||||
<div className="relative overflow-hidden text-xs wrap-all">
|
||||
<CopyButton value={code} className="absolute right-4 top-2" />
|
||||
<SyntaxHighlighter
|
||||
language={language}
|
||||
style={materialDark}
|
||||
showLineNumbers={showLineNumbers}
|
||||
wrapLongLines={wrapLongLines}
|
||||
lineProps={lineProps}
|
||||
customStyle={{
|
||||
margin: 0,
|
||||
paddingRight: showLineNumbers ? 0 : 24,
|
||||
paddingLeft: showLineNumbers ? 0 : 24,
|
||||
borderRadius: "var(--radius)",
|
||||
height: 600,
|
||||
height: 500,
|
||||
whiteSpace: wrapLongLines ? "pre-wrap" : "pre",
|
||||
overflowWrap: wrapLongLines ? "normal" : "break-word",
|
||||
}}
|
||||
>
|
||||
{code}
|
||||
|
||||
@@ -1,17 +1,39 @@
|
||||
import { UrlPatternTip } from "@/components/tips/url-pattern-tip.tsx";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx";
|
||||
import { Input } from "@/components/ui/input.tsx";
|
||||
import { Label } from "@/components/ui/label.tsx";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select.tsx";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select.tsx";
|
||||
import { Separator } from "@/components/ui/separator.tsx";
|
||||
import { Switch } from "@/components/ui/switch.tsx";
|
||||
import { FormSchema } from "@/types/schema.ts";
|
||||
import { MainConfig } from "@/types/shell.ts";
|
||||
import { ServerIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { FormProvider, UseFormReturn } from "react-hook-form";
|
||||
|
||||
const JDKVersion = [
|
||||
{ name: "Java6", value: "50" },
|
||||
{ name: "Java8", value: "52" },
|
||||
{ name: "Java9", value: "53" },
|
||||
{ name: "Java11", value: "55" },
|
||||
{ name: "Java17", value: "61" },
|
||||
{ name: "Java21", value: "65" },
|
||||
];
|
||||
|
||||
export function MainConfigCard({
|
||||
mainConfig,
|
||||
form,
|
||||
servers,
|
||||
}: {
|
||||
mainConfig: MainConfig | undefined;
|
||||
form: UseFormReturn<FormSchema>;
|
||||
servers?: string[];
|
||||
}) {
|
||||
const [shellToolMap, setShellToolMap] = useState<{ [toolName: string]: string[] }>();
|
||||
const [shellTools, setShellTools] = useState<string[]>([]);
|
||||
const [shellTypes, setShellTypes] = useState<string[]>([]);
|
||||
|
||||
export function MainConfigCard() {
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader className="pb-1">
|
||||
@@ -20,87 +42,280 @@ export function MainConfigCard() {
|
||||
<span>核心配置</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="server" className="text-sm">
|
||||
目标服务
|
||||
<FormProvider {...form}>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="server"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel>目标服务</FormLabel>
|
||||
<Select
|
||||
onValueChange={(v) => {
|
||||
field.onChange(v);
|
||||
if (mainConfig) {
|
||||
setShellToolMap(mainConfig[v]);
|
||||
setShellTools(Object.keys(mainConfig[v]));
|
||||
setShellTypes([]);
|
||||
}
|
||||
}}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue placeholder="请选择" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{servers?.map((server: string) => (
|
||||
<SelectItem key={server} value={server}>
|
||||
{server}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="targetJdkVersion"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel>JRE(可选)</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue placeholder="请选择" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{JDKVersion.map((v) => (
|
||||
<SelectItem key={v.value} value={v.value}>
|
||||
{v.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 mt-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="debug"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Switch id="debug" checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
<FormLabel htmlFor="debug">开启调试</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="bypassJavaModule"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center space-x-2 space-y-0">
|
||||
<FormControl>
|
||||
<Switch id="bypassJavaModule" checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
<Label htmlFor="bypassJavaModule">bypassJavaModule</Label>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="lambda" disabled />
|
||||
<Label htmlFor="lambda">Lambda 类名 (WIP)</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="obfuscate" disabled />
|
||||
<Label htmlFor="obfuscate">开启混淆 (WIP)</Label>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="mt-4 mb-2" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="shellClassName"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel>内存马类名(可选)</FormLabel>
|
||||
<Input id="shellClassName" {...field} placeholder="请输入" className="h-8" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="grid grid-cols-3 gap-2 mt-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="shellTool"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel>内存马工具类型</FormLabel>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(value: string) => {
|
||||
field.onChange(value);
|
||||
if (shellToolMap) {
|
||||
setShellTypes(shellToolMap[value]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue placeholder="请选择" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{shellTools.length ? (
|
||||
shellTools.map((v) => (
|
||||
<SelectItem key={v} value={v}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value=" ">请先选择目标服务</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="shellType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel>内存马挂载类型</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="h-8">
|
||||
<SelectValue placeholder="请选择" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{shellTypes.length ? (
|
||||
shellTypes.map((v) => (
|
||||
<SelectItem key={v} value={v}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value=" ">请先选择内存马工具类型</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="urlPattern"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col mt-1">
|
||||
<Label className="flex items-center">
|
||||
请求路径 <UrlPatternTip />
|
||||
</Label>
|
||||
<Input {...field} placeholder="请输入" className="h-8" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
{form.getValues().shellTool === "Godzilla" && (
|
||||
<div className="space-y-1">
|
||||
<Label>Godzilla 配置</Label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="godzillaPass"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1 flex items-center justify-start">
|
||||
<Label className="text-xs whitespace-nowrap w-1/2">密码:</Label>
|
||||
<Input {...field} placeholder="Pass" className="h-8" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="godzillaKey"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1 flex items-center justify-start">
|
||||
<Label className="text-xs whitespace-nowrap w-1/2">密钥:</Label>
|
||||
<Input {...field} placeholder="Key" className="h-8" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="godzillaHeaderName"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1 flex items-center justify-start">
|
||||
<Label className="text-xs whitespace-nowrap w-1/2">请求头键:</Label>
|
||||
<Input {...field} placeholder="Header Name" className="h-8" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="godzillaHeaderValue"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1 flex items-center justify-start">
|
||||
<Label className="text-xs whitespace-nowrap w-1/2">请求头值:</Label>
|
||||
<Input {...field} placeholder="Header Value" className="h-8" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{form.getValues().shellTool === "Command" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="commandParamName"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel>请求参数</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="请输入" className="h-8" />
|
||||
</FormControl>
|
||||
<FormDescription>填写接收命令的请求参数,例如填 cmd 即 `?cmd=whoami` 来执行命令</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Separator className="mt-4 mb-2" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="injectorClassName"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel>注入器类名(可选)</FormLabel>
|
||||
<Input id="injectorClassName" {...field} placeholder="请输入" className="h-8" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="space-y-1 mt-2">
|
||||
<Label htmlFor="interface" className="flex items-center gap-2">
|
||||
注入器类继承(可选)(WIP)
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="server" className="h-8">
|
||||
<Select disabled>
|
||||
<SelectTrigger id="interface" className="h-8">
|
||||
<SelectValue placeholder="请选择" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="tomcat">Tomcat</SelectItem>
|
||||
<SelectItem value="jetty">Jetty</SelectItem>
|
||||
<SelectItem value="undertow">Undertow</SelectItem>
|
||||
<SelectItem value="jboss">JBoss</SelectItem>
|
||||
<SelectItem value="wildfly">Wildfly</SelectItem>
|
||||
<SelectItem value="springmvc">SpringMVC</SelectItem>
|
||||
<SelectItem value="springwebflux">SpringWebflux</SelectItem>
|
||||
<SelectItem value="weblogic">WebLogic</SelectItem>
|
||||
<SelectItem value="websphere">WebSphere</SelectItem>
|
||||
<SelectItem value="resin">Resin</SelectItem>
|
||||
<SelectItem value="glassfish">Glassfish</SelectItem>
|
||||
<SelectItem value="bes">BES</SelectItem>
|
||||
<SelectItem value="tongweb">TongWeb</SelectItem>
|
||||
<SelectItem value="JDK_AbstractTranslet">JDK_AbstractTranslet</SelectItem>
|
||||
<SelectItem value="XALAN_AbstractTranslet">XALAN_AbstractTranslet</SelectItem>
|
||||
<SelectItem value="FASTJSON_GroovyASTTransformation">FASTJSON_GroovyASTTransformation</SelectItem>
|
||||
<SelectItem value="SnakeYaml_ScriptEngineFactory">SnakeYaml_ScriptEngineFactory</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="targetJdkVersion" className="text-sm">
|
||||
JRE
|
||||
</Label>
|
||||
<Select defaultValue="6">
|
||||
<SelectTrigger id="targetJdkVersion" className="h-8">
|
||||
<SelectValue placeholder="请选择" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="6">Java 6</SelectItem>
|
||||
<SelectItem value="7">Java 7</SelectItem>
|
||||
<SelectItem value="8">Java 8</SelectItem>
|
||||
<SelectItem value="9">Java 9</SelectItem>
|
||||
<SelectItem value="11">Java 11</SelectItem>
|
||||
<SelectItem value="17">Java 17</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label htmlFor="shellClassName" className="text-sm">
|
||||
自定义内存马类名(可选)
|
||||
</Label>
|
||||
<Input id="shellClassName" placeholder="请输入" className="h-8" />
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="injectorClassName" className="text-sm">
|
||||
自定义注入器类名(可选)
|
||||
</Label>
|
||||
<Input id="injectorClassName" placeholder="请输入" className="h-8" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="obfuscate" />
|
||||
<Label htmlFor="obfuscate" className="text-sm">
|
||||
开启混淆
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="debug" />
|
||||
<Label htmlFor="debug" className="text-sm">
|
||||
开启调试
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="lambda" />
|
||||
<Label htmlFor="debug" className="text-sm">
|
||||
Lambda 类名
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CardContent>
|
||||
</FormProvider>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
|
||||
import { Label } from "@/components/ui/label.tsx";
|
||||
import { FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form.tsx";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx";
|
||||
import { FormSchema } from "@/types/schema.ts";
|
||||
import { PackerConfig } from "@/types/shell.ts";
|
||||
import { PackageIcon } from "lucide-react";
|
||||
import { FormProvider, UseFormReturn } from "react-hook-form";
|
||||
|
||||
export function PackageConfigCard() {
|
||||
export function PackageConfigCard({
|
||||
packerConfig,
|
||||
form,
|
||||
}: {
|
||||
packerConfig: PackerConfig | undefined;
|
||||
form: UseFormReturn<FormSchema>;
|
||||
}) {
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader className="pb-1">
|
||||
@@ -12,74 +21,32 @@ export function PackageConfigCard() {
|
||||
<span>打包配置</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-sm">打包方式</Label>
|
||||
<RadioGroup defaultValue="base64">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="base64" id="base64" />
|
||||
<Label htmlFor="base64" className="text-xs">
|
||||
Base64
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="jsp" id="jsp" />
|
||||
<Label htmlFor="jsp" className="text-xs">
|
||||
JSP
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="classFile" id="classFile" />
|
||||
<Label htmlFor="classFile" className="text-xs">
|
||||
反序列化
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="scriptEngine" id="scriptEngine" />
|
||||
<Label htmlFor="scriptEngine" className="text-xs">
|
||||
ScriptEngine
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="elExpression" id="elExpression" />
|
||||
<Label htmlFor="elExpression" className="text-xs">
|
||||
EL Expression
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="ognlExpression" id="ognlExpression" />
|
||||
<Label htmlFor="ognlExpression" className="text-xs">
|
||||
OGNL Expression
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="spelExpression" id="spelExpression" />
|
||||
<Label htmlFor="spelExpression" className="text-xs">
|
||||
SpEL Expression
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="elExpression" id="elExpression" />
|
||||
<Label htmlFor="elExpression" className="text-xs">
|
||||
EL Expression
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="freemarkerExpression" id="freemarkerExpression" />
|
||||
<Label htmlFor="freemarkerExpression" className="text-xs">
|
||||
Freemarker
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="velocityExpression" id="velocityExpression" />
|
||||
<Label htmlFor="velocityExpression" className="text-xs">
|
||||
Velocity
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<CardContent>
|
||||
<FormProvider {...form}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="packingMethod"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>打包方式</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup onValueChange={field.onChange} defaultValue={field.value} className="grid grid-cols-3">
|
||||
{Object.entries(packerConfig ?? {}).map(([name, value]) => (
|
||||
<FormItem key={value} className="flex items-center space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value={value} id={value} />
|
||||
</FormControl>
|
||||
<FormLabel className="text-xs" htmlFor={value}>
|
||||
{name}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</FormProvider>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card.tsx";
|
||||
import { Input } from "@/components/ui/input.tsx";
|
||||
import { Label } from "@/components/ui/label.tsx";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select.tsx";
|
||||
import { FishSymbolIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export function ShellConfigCard() {
|
||||
const [shellTool, setShellTool] = useState<string>("");
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader className="pb-1">
|
||||
<CardTitle className="text-md flex items-center gap-2">
|
||||
<FishSymbolIcon className="h-5" />
|
||||
<span>内存马配置</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="shellType" className="text-sm">
|
||||
挂载类型
|
||||
</Label>
|
||||
<Select>
|
||||
<SelectTrigger id="shellType" className="h-8">
|
||||
<SelectValue placeholder="请选择" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="filter">Filter</SelectItem>
|
||||
<SelectItem value="servlet">Servlet</SelectItem>
|
||||
<SelectItem value="listener">Listener</SelectItem>
|
||||
<SelectItem value="agent">Agent</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="shellTool" className="text-sm">
|
||||
工具类型
|
||||
</Label>
|
||||
<Select onValueChange={(value: string) => setShellTool(value)}>
|
||||
<SelectTrigger id="shellTool" className="h-8">
|
||||
<SelectValue placeholder="请选择" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="command">命令执行</SelectItem>
|
||||
<SelectItem value="fileList">File List</SelectItem>
|
||||
<SelectItem value="gozilla">Godzilla</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{shellTool === "gozilla" && (
|
||||
<div className="space-y-1">
|
||||
<Label className="text-sm">Godzilla 配置</Label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Input placeholder="Pass" className="h-8 text-sm" />
|
||||
<Input placeholder="Key" className="h-8 text-sm" />
|
||||
<Input placeholder="Header Name" className="h-8 text-sm" />
|
||||
<Input placeholder="Header Value" className="h-8 text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{shellTool === "command" && (
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="paramName" className="text-sm">
|
||||
接收命令请求参数
|
||||
</Label>
|
||||
<Input id="paramName" placeholder="请输入" className="h-8 text-sm" />
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -26,9 +26,7 @@ export function ThemeProvider({
|
||||
storageKey = "vite-ui-theme",
|
||||
...props
|
||||
}: ThemeProviderProps) {
|
||||
const [theme, setTheme] = useState<Theme>(
|
||||
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
|
||||
);
|
||||
const [theme, setTheme] = useState<Theme>(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme);
|
||||
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement;
|
||||
@@ -37,9 +35,7 @@ export function ThemeProvider({
|
||||
|
||||
root.removeAttribute(mode);
|
||||
if (theme === "system") {
|
||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
|
||||
root.classList.add(systemTheme);
|
||||
root.setAttribute(mode, systemTheme);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
|
||||
export function JRETip() {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon className="cursor-pointer h-3" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>目标 JRE 版本,一般而言为了最大的兼容性,默认 Java 6 即可。</p>
|
||||
<p>特定情况下,例如 JDK8 才能使用 lambda 表达式,JDK9 以上存在模块限制时才需要选择特定的版本。</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip.tsx";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
|
||||
export function UrlPatternTip() {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon className="cursor-pointer h-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>当使用 Servlet 内存马时必须写具体的 urlPattern,不能使用 /*,不然无法使用</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
@@ -9,8 +9,7 @@ const alertVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-background text-foreground",
|
||||
destructive:
|
||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -29,21 +28,16 @@ Alert.displayName = "Alert";
|
||||
|
||||
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<h5
|
||||
ref={ref}
|
||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
<h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />
|
||||
),
|
||||
);
|
||||
AlertTitle.displayName = "AlertTitle";
|
||||
|
||||
const AlertDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
|
||||
));
|
||||
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
|
||||
),
|
||||
);
|
||||
AlertDescription.displayName = "AlertDescription";
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription };
|
||||
|
||||
@@ -11,8 +11,7 @@ const buttonVariants = cva(
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
@@ -40,9 +39,7 @@ export interface ButtonProps
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
|
||||
);
|
||||
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
@@ -2,15 +2,9 @@ import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("rounded-xl border bg-card text-card-foreground shadow", className)} {...props} />
|
||||
));
|
||||
Card.displayName = "Card";
|
||||
|
||||
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
@@ -22,11 +16,7 @@ CardHeader.displayName = "CardHeader";
|
||||
|
||||
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
<div ref={ref} className={cn("font-semibold leading-none tracking-tight", className)} {...props} />
|
||||
),
|
||||
);
|
||||
CardTitle.displayName = "CardTitle";
|
||||
@@ -39,9 +29,7 @@ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HT
|
||||
CardDescription.displayName = "CardDescription";
|
||||
|
||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
),
|
||||
({ className, ...props }, ref) => <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />,
|
||||
);
|
||||
CardContent.displayName = "CardContent";
|
||||
|
||||
|
||||
@@ -152,18 +152,12 @@ const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
<DropdownMenuPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
||||
));
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
||||
|
||||
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
|
||||
);
|
||||
return <span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />;
|
||||
};
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from "react-hook-form";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState, formState } = useFormContext();
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
||||
|
||||
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => {
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
},
|
||||
);
|
||||
FormItem.displayName = "FormItem";
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return <Label ref={ref} className={cn(error && "text-destructive", className)} htmlFor={formItemId} {...props} />;
|
||||
});
|
||||
FormLabel.displayName = "FormLabel";
|
||||
|
||||
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
|
||||
({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
FormControl.displayName = "FormControl";
|
||||
|
||||
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||
({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p ref={ref} id={formDescriptionId} className={cn("text-[0.8rem] text-muted-foreground", className)} {...props} />
|
||||
);
|
||||
},
|
||||
);
|
||||
FormDescription.displayName = "FormDescription";
|
||||
|
||||
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||
({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("text-[0.8rem] font-medium text-destructive", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
);
|
||||
},
|
||||
);
|
||||
FormMessage.displayName = "FormMessage";
|
||||
|
||||
export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField };
|
||||
@@ -6,9 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
);
|
||||
const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70");
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
|
||||
@@ -94,11 +94,7 @@ const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
<SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} {...props} />
|
||||
));
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||
|
||||
@@ -128,11 +124,7 @@ const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
<SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
||||
));
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as React from "react";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn("shrink-0 bg-border", orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||
|
||||
export { Separator };
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Toaster as Sonner } from "sonner";
|
||||
import { useTheme } from "@/components/theme-provider.tsx";
|
||||
|
||||
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner theme={theme as ToasterProps["theme"]} richColors className="toaster group" toastOptions={{}} {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
export { Toaster };
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as React from "react";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const TooltipProvider = TooltipPrimitive.Provider;
|
||||
|
||||
const Tooltip = TooltipPrimitive.Root;
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
|
||||
const TooltipContent = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground 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",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</TooltipPrimitive.Portal>
|
||||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
Reference in New Issue
Block a user