From 5aae3c4dbe7963abf8b31e0b9e5569ff8852266c Mon Sep 17 00:00:00 2001 From: ReaJason Date: Wed, 13 Aug 2025 22:18:34 +0800 Subject: [PATCH] feat: add about page --- web/src/components/layouts/root-layout.tsx | 17 +- web/src/components/ui/avatar.tsx | 51 +++ web/src/components/version-badge.tsx | 90 ------ web/src/lib/config.ts | 4 + web/src/pages/about.tsx | 342 +++++++++++++++++++++ web/src/router.tsx | 5 + 6 files changed, 413 insertions(+), 96 deletions(-) create mode 100644 web/src/components/ui/avatar.tsx delete mode 100644 web/src/components/version-badge.tsx create mode 100644 web/src/pages/about.tsx diff --git a/web/src/components/layouts/root-layout.tsx b/web/src/components/layouts/root-layout.tsx index 89a5f1ce..e56303d7 100644 --- a/web/src/components/layouts/root-layout.tsx +++ b/web/src/components/layouts/root-layout.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from "react-i18next"; import { NavLink } from "react-router"; import { Outlet } from "react-router-dom"; import { LanguageSwitcher } from "@/components/language-switcher"; @@ -5,12 +6,12 @@ import { ModeToggle } from "@/components/mode-toggle.tsx"; import { ThemeProvider } from "@/components/theme-provider.tsx"; import { Button } from "@/components/ui/button"; import { Toaster } from "@/components/ui/sonner"; -import VersionBadge from "@/components/version-badge"; import { GitHubIcon } from "@/icon"; import { siteConfig } from "@/lib/config"; import { MobileNav } from "../modile-nav"; export default function RootLayout() { + const { t } = useTranslation("common"); return ( @@ -19,25 +20,29 @@ export default function RootLayout() {
- - MemShellParty + + {siteConfig.name}
- diff --git a/web/src/components/ui/avatar.tsx b/web/src/components/ui/avatar.tsx new file mode 100644 index 00000000..417c29f1 --- /dev/null +++ b/web/src/components/ui/avatar.tsx @@ -0,0 +1,51 @@ +import * as React from "react" +import {Avatar as AvatarPrimitive} from "radix-ui" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/web/src/components/version-badge.tsx b/web/src/components/version-badge.tsx deleted file mode 100644 index df6c120f..00000000 --- a/web/src/components/version-badge.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { CircleX, LoaderCircle, RefreshCcw } from "lucide-react"; -import type React from "react"; -import { useTranslation } from "react-i18next"; -import { env } from "@/config"; -import { Button } from "./ui/button"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip"; - -type VersionInfo = { - currentVersion: string; - latestVersion: string; - hasUpdate: boolean; -}; - -const VersionBadge: React.FC = () => { - const { isPending, data, isError } = useQuery({ - queryKey: ["version"], - queryFn: async () => { - const response = await fetch(`${env.API_URL}/version`); - if (response.ok) { - return await response.json(); - } - return "unknown"; - }, - }); - const inProduction = env.MODE === "production"; - const { t } = useTranslation(); - - return ( -
- {isPending && ( - - )} - {isError && ( - - )} - {data?.hasUpdate && inProduction && ( - - - - - - -

- {t("version.updateAvailableTooltip", { - currentVersion: data.currentVersion, - latestVersion: data.latestVersion, - })} -

-
-
-
- )} - {data && (!data.hasUpdate || !inProduction) && ( - - )} -
- ); -}; - -export default VersionBadge; diff --git a/web/src/lib/config.ts b/web/src/lib/config.ts index 3f742e12..b13d7d6b 100644 --- a/web/src/lib/config.ts +++ b/web/src/lib/config.ts @@ -17,5 +17,9 @@ export const siteConfig = { href: "/probeshell", label: "ProbeShellGenerator", }, + { + href: "/about", + label: "about", + }, ], }; diff --git a/web/src/pages/about.tsx b/web/src/pages/about.tsx new file mode 100644 index 00000000..964c1f52 --- /dev/null +++ b/web/src/pages/about.tsx @@ -0,0 +1,342 @@ +import { useQuery } from "@tanstack/react-query"; +import { motion } from "framer-motion"; +import { + AlertCircle, + Code, + Download, + ExternalLink, + Github, + Globe, + Heart, + Mail, + Package, + Shield, + User, +} from "lucide-react"; +import { LineShadowText } from "@/components/magicui/line-shadow-text"; +import { useTheme } from "@/components/theme-provider"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardFooter } from "@/components/ui/card"; +import { env } from "@/config"; +import { siteConfig } from "@/lib/config"; + +type VersionInfo = { + currentVersion: string; + latestVersion: string; + hasUpdate: boolean; + releaseUrl: string; +}; + +export default function AboutPage() { + const theme = useTheme(); + const shadowColor = theme.theme === "dark" ? "#ffffff" : "#000000"; + const { + data: updateInfo, + isPending, + error, + } = useQuery({ + queryKey: ["version"], + queryFn: async () => { + const response = await fetch(`${env.API_URL}/version`); + if (response.ok) { + return await response.json(); + } + return "unknown"; + }, + }); + const inProduction = true; + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + }, + }, + }; + + const itemVariants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { + duration: 0.5, + }, + }, + }; + + return ( +
+
+
+
+ +
+ + + For Security Research & Authorized Testing Only + +
+

+ + MemShell + + Party + + +

+

+ A self-hosted, visual platform for one-click generation of Java + memory shells for common middleware and frameworks. The ultimate + learning platform for security researchers. +

+
+
+
+ + {updateInfo?.hasUpdate && inProduction && ( + + + + + + New version {updateInfo.latestVersion} is available! (Current:{" "} + {updateInfo.currentVersion}) + + + + + + + + )} + + +
+ + + +
+ +

Version

+
+
+
+ + Current Version + + + {updateInfo?.currentVersion || "v0.0.0"} + +
+
+ + Latest Version + + {isPending && ( + Checking... + )} + {error && ( + {error.message} + )} + {updateInfo && !error && ( + + {updateInfo.latestVersion} + + )} +
+
+ License + MIT License +
+
+
+ + + Last test time: {new Date().toLocaleString()} + + +
+
+ + + + +
+ +

Author

+
+
+
+ + + RJ + +
+

+ {siteConfig.author} +

+

+ {siteConfig.authorIntro} +

+
+
+ +
+
+
+
+
+
+ + +
+
+

Resources & Links

+

+ Explore documentation and contribute to the project +

+
+
+ + + +

Documentation

+

+ Comprehensive guides and API references for using + MemShellParty effectively. +

+ + Read Docs + +
+
+ + + + +

Source Code

+

+ View the source code, report issues, and contribute to the + development. +

+ + View Repository + +
+
+ + + + +

Support

+

+ Star the project on GitHub and share it with the security + community. +

+ + Star on GitHub + +
+
+
+
+
+ +
+
+
+

{siteConfig.name}

+

+ Built with ❤️ by{" "} + + {siteConfig.author} + +

+
+
+ © 2025 {siteConfig.name}. For authorized security testing only. +
+
+
+
+ ); +} diff --git a/web/src/router.tsx b/web/src/router.tsx index 70bb6aa1..c7f19fae 100644 --- a/web/src/router.tsx +++ b/web/src/router.tsx @@ -4,6 +4,7 @@ import { env } from "@/config"; import MemShellPage from "@/pages/memshell"; import ProbeShellPage from "@/pages/probeshell"; import type { MemShellFormSchema } from "@/types/schema"; +import AboutPage from "@/pages/about"; // Function to parse URL parameters into form default values const parseUrlParams = ( @@ -89,6 +90,10 @@ export const router = createHashRouter( path: "probeshell", element: , }, + { + path: "about", + element: , + }, ], }, ],