feat: support Tomcat Command WebSocketBypassNginx

This commit is contained in:
ReaJason
2026-01-18 23:49:41 +08:00
parent 8458becfbc
commit 5b5c225edb
23 changed files with 849 additions and 53 deletions
+12 -4
View File
@@ -1,21 +1,29 @@
import { Check, Copy } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import {
type ComponentPropsWithoutRef,
useCallback,
useEffect,
useState,
} from "react";
import CopyToClipboard from "react-copy-to-clipboard";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { cn } from "@/lib/utils";
interface CopyableFieldProps {
type CopyableFieldProps = {
label: string;
value?: string;
text?: string;
}
} & Omit<ComponentPropsWithoutRef<"div">, "children">;
export function CopyableField({
label,
value,
text,
className,
...divProps
}: Readonly<CopyableFieldProps>) {
const [hasCopied, setHasCopied] = useState(false);
const { t } = useTranslation(["common"]);
@@ -39,7 +47,7 @@ export function CopyableField({
}, [hasCopied, label, t]);
return (
<div className="flex flex-col gap-1 py-1">
<div className={cn("flex flex-col gap-1 py-1", className)} {...divProps}>
<div className="flex items-center justify-between gap-2 h-6">
<Label className="text-sm text-muted-foreground">{label}</Label>
{value && (
@@ -1,4 +1,5 @@
import { FileTextIcon } from "lucide-react";
import { Fragment } from "react/jsx-runtime";
import { useTranslation } from "react-i18next";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
@@ -45,13 +46,12 @@ export function BasicInfo({
label={t("mainConfig.shellMountType")}
text={generateResult?.shellConfig.shellType}
/>
{!notNeedUrlPattern(generateResult?.shellConfig?.shellType) && (
<CopyableField
label={t("mainConfig.urlPattern")}
text={generateResult?.injectorConfig.urlPattern}
value={generateResult?.injectorConfig.urlPattern}
/>
)}
<CopyableField
hidden={notNeedUrlPattern(generateResult?.shellConfig?.shellType)}
label={t("common:urlPattern")}
text={generateResult?.injectorConfig.urlPattern}
value={generateResult?.injectorConfig.urlPattern}
/>
</div>
{generateResult?.shellConfig.shellTool !== ShellToolType.Custom && (
<Separator className="my-1" />
@@ -121,17 +121,35 @@ export function BasicInfo({
</>
)}
{generateResult?.shellConfig.shellTool === ShellToolType.Command && (
<CopyableField
label={t("common:paramName")}
text={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
value={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
/>
<Fragment>
<CopyableField
hidden={generateResult?.shellConfig.shellType.includes(
"WebSocket",
)}
label={t("common:paramName")}
text={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
value={
(generateResult?.shellToolConfig as CommandShellToolConfig)
.paramName
}
/>
<CopyableField
hidden={
!(
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.Suo5v2) && (
@@ -1,7 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import { ChevronDown, ChevronRight, InfoIcon } from "lucide-react";
import { useState } from "react";
import { Controller, type UseFormReturn } from "react-hook-form";
import { Controller, type UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import {
@@ -38,6 +38,10 @@ export function CommandTabContent({
}>) {
const { t } = useTranslation(["memshell", "common"]);
const [isAdvancedOpen, setIsAdvancedOpen] = useState(false);
const shellType = useWatch({
name: "shellType",
control: form.control,
});
const { data } = useQuery<{
encryptors: Array<string>;
implementationClasses: Array<string>;
@@ -58,7 +62,7 @@ export function CommandTabContent({
control={form.control}
name="commandParamName"
render={({ field }) => (
<Field className="gap-1">
<Field className="gap-1" hidden={shellType.includes("WebSocket")}>
<div className="flex items-center gap-1">
<FieldLabel>
{t("common:paramName")} {t("common:optional")}
@@ -79,7 +83,42 @@ export function CommandTabContent({
</Field>
)}
/>
<div
className="grid grid-cols-1 md:grid-cols-2 gap-2"
hidden={
shellType !== "BypassNginxWebSocket" &&
shellType !== "BypassNginxJakartaWebSocket"
}
>
<Controller
control={form.control}
name="headerName"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>{t("common:headerName")}</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
<Controller
control={form.control}
name="headerValue"
render={({ field }) => (
<Field className="gap-1">
<FieldLabel>
{t("common:headerValue")} {t("common:optional")}
</FieldLabel>
<Input
{...field}
placeholder={t("common:placeholders.input")}
/>
</Field>
)}
/>
</div>
<Collapsible open={isAdvancedOpen} onOpenChange={setIsAdvancedOpen}>
<CollapsibleTrigger className="flex items-center gap-2 w-full py-2 text-sm font-medium hover:underline">
{isAdvancedOpen ? (
+2
View File
@@ -30,6 +30,8 @@ export interface ShellToolConfig {
export interface CommandShellToolConfig {
shellClassName?: string;
paramName?: string;
headerName?: string;
headerValue?: string;
}
export interface GodzillaShellToolConfig {