From 9d22293ae8b0f2c60fb7d90e1e739836cf5bbbe2 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 17 Jul 2025 06:42:40 +0200 Subject: [PATCH] server deletion --- .gitignore | 5 +- package-lock.json | 98 +++++++++++ package.json | 2 + sentryx/servers.json | 1 - sentryx/sessions.json | 1 - sentryx/users.json | 1 - src/app/api/auth/route.ts | 4 +- src/app/api/servers/route.ts | 32 +++- src/app/dashboard/page.tsx | 19 ++- src/app/dashboard/server.tsx | 156 +++++++++++++----- src/app/servers/page.tsx | 19 ++- src/components/ui/alert-dialog.tsx | 157 ++++++++++++++++++ src/components/ui/context-menu.tsx | 252 +++++++++++++++++++++++++++++ src/lib/session.ts | 6 + 14 files changed, 698 insertions(+), 55 deletions(-) delete mode 100644 sentryx/servers.json delete mode 100644 sentryx/sessions.json delete mode 100644 sentryx/users.json create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/context-menu.tsx diff --git a/.gitignore b/.gitignore index 7be0664..7429c78 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ !.yarn/plugins !.yarn/releases !.yarn/versions +/sentryx # testing /coverage @@ -38,6 +39,4 @@ yarn-error.log* # typescript *.tsbuildinfo -next-env.d.ts - -sentryx/ \ No newline at end of file +next-env.d.ts \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d5549f4..c0bf947 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "sentryx", "version": "0.1.0", "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.14", + "@radix-ui/react-context-menu": "^2.2.15", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", @@ -811,6 +813,34 @@ "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==", "license": "MIT" }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.14.tgz", + "integrity": "sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dialog": "1.1.14", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", @@ -890,6 +920,34 @@ } } }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.15.tgz", + "integrity": "sha512-UsQUMjcYTsBjTSXw0P3GO0werEQvUY2plgRQuKoCTtkNr45q1DiL51j4m7gxhABzZ0BadoXNsIbg7F3KwiUBbw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.15", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", @@ -1049,6 +1107,46 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.15.tgz", + "integrity": "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", diff --git a/package.json b/package.json index 4c04bcc..6a0529c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "lint": "next lint" }, "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.14", + "@radix-ui/react-context-menu": "^2.2.15", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-progress": "^1.1.7", diff --git a/sentryx/servers.json b/sentryx/servers.json deleted file mode 100644 index 3728012..0000000 --- a/sentryx/servers.json +++ /dev/null @@ -1 +0,0 @@ -[{"name":"name","ip":"pass"},{"name":"name","ip":"pass"}] \ No newline at end of file diff --git a/sentryx/sessions.json b/sentryx/sessions.json deleted file mode 100644 index ef98b76..0000000 --- a/sentryx/sessions.json +++ /dev/null @@ -1 +0,0 @@ -{"SESH-56474656-1b64-42d7-88e2-e0ed6d1f2084":"admin","SESH-fde4fa01-2f88-4ae4-946e-5c53ffe75a18":"admin","SESH-badb4d6c-e4e9-4f42-b4b9-a46d8de5d227":"admin","SESH-cf69dc29-ad13-42c7-96a1-f6d5d6584b46":"admin","SESH-582e766f-f51b-4c55-bcf6-9aaf9ff859a3":"admin","SESH-029c0d01-9dc1-4efd-bfbe-ee5c645ef38d":"admin"} \ No newline at end of file diff --git a/sentryx/users.json b/sentryx/users.json deleted file mode 100644 index 11306b5..0000000 --- a/sentryx/users.json +++ /dev/null @@ -1 +0,0 @@ -{"admin":{"username":"admin","password":"admin"}} \ No newline at end of file diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts index 97033fe..4b7a756 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -55,6 +55,6 @@ export async function GET(request: Request) { return NextResponse.json({ message: "ok" }); } -export default function validate(sessionId: string) { - return sessions.data[sessionId] != undefined; +export function validate(sessionId: string | null) { + return sessionId && sessions.data[sessionId] != undefined; } diff --git a/src/app/api/servers/route.ts b/src/app/api/servers/route.ts index b52d3c2..c67a31a 100644 --- a/src/app/api/servers/route.ts +++ b/src/app/api/servers/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; import Data from "../data"; import Server, { ServerAPI } from "@/types/server"; import { HttpStatusCode } from "axios"; -import validate from "../auth/route"; +import { validate } from "../auth/route"; const servers = new Data("sentryx/servers.json", []); @@ -73,3 +73,33 @@ export async function POST(request: Request) { message: "ok", }); } + +export async function DELETE(request: Request) { + const { searchParams } = new URL(request.url); + const sessionId = searchParams.get("sessionId"); + const index = searchParams.get("index"); + + if (!validate(sessionId)) + return NextResponse.json( + { message: "Invalid session" }, + { status: HttpStatusCode.Unauthorized } + ); + + if (!index) + return NextResponse.json( + { + message: "The index is invalid", + }, + { + status: HttpStatusCode.BadRequest, + } + ); + + servers.data.splice(parseInt(index), 1); + + servers.write(); + + return NextResponse.json({ + message: "ok", + }); +} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 2a842b7..7fde509 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -13,6 +13,7 @@ import { Button } from "@/components/ui/button"; import { useEffect, useState } from "react"; import useSession from "@/hooks/useSession"; import Server from "@/types/server"; +import { toast } from "sonner"; export function generateHourlyData() { const result = []; @@ -191,7 +192,23 @@ export default function Dashboard() {
{searchedServers.map((s, i) => ( - + + session + ?.removeServer(i) + .then(() => { + toast.info("Server successfully deleted"); + }) + .catch((e) => { + console.log(e.response.data.message); + toast.error( + `Error deleting server: ` + e.response.data.message + ); + }) + } + /> ))}
diff --git a/src/app/dashboard/server.tsx b/src/app/dashboard/server.tsx index fca216f..cc7e9c0 100644 --- a/src/app/dashboard/server.tsx +++ b/src/app/dashboard/server.tsx @@ -1,3 +1,13 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import { Card, CardContent, @@ -5,6 +15,12 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuTrigger, +} from "@/components/ui/context-menu"; import { Label } from "@/components/ui/label"; import { Progress } from "@/components/ui/progress"; import Server from "@/types/server"; @@ -14,8 +30,11 @@ import { MapPin, MemoryStick, Network, + Pencil, + Trash, Wifi, } from "lucide-react"; +import { useState } from "react"; function UsageBar({ value, @@ -41,50 +60,99 @@ function UsageBar({ ); } -export default function ServerComponent({ server }: { server: Server }) { +export default function ServerComponent({ + server, + onDelete, +}: { + server: Server; + onDelete: () => void; +}) { + const [deleteAlert, setDeleteAlert] = useState(false); + return ( - - - {server.name} - -
- {server.status[0].toUpperCase() + server.status.slice(1)} {" · "} - {server.ip} - {server.location && ( -
- - {server.location} -
- )} -
-
-
- - - - - - -
+ <> + + + + + {server.name} + +
+ {server.status[0].toUpperCase() + server.status.slice(1)}{" "} + {" · "} + {server.ip} + {server.location && ( +
+ + {server.location} +
+ )} +
+
+
+ + + + + + +
+
+ + + + Edit + + setDeleteAlert(true)} + > + + Delete + + +
+ + + + + Are you sure? + + This action will permanently remove your server from the server + list + + + + Cancel + + Delete + + + + + ); } diff --git a/src/app/servers/page.tsx b/src/app/servers/page.tsx index f3c9083..0cca797 100644 --- a/src/app/servers/page.tsx +++ b/src/app/servers/page.tsx @@ -9,6 +9,7 @@ import Server from "@/types/server"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import Map from "./Map"; import NewServerDialog from "./NewServerDialog"; +import { toast } from "sonner"; export default function Servers() { const session = useSession(); @@ -59,7 +60,23 @@ export default function Servers() {
{searchedServers.map((s, i) => ( - + + session + ?.removeServer(i) + .then(() => { + toast.info("Server successfully deleted"); + }) + .catch((e) => { + console.log(e.response.data.message); + toast.error( + `Error deleting server: ` + e.response.data.message + ); + }) + } + /> ))}
diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..0863e40 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/src/components/ui/context-menu.tsx b/src/components/ui/context-menu.tsx new file mode 100644 index 0000000..e29ad05 --- /dev/null +++ b/src/components/ui/context-menu.tsx @@ -0,0 +1,252 @@ +"use client" + +import * as React from "react" +import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function ContextMenu({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function ContextMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function ContextMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function ContextMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function ContextMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuCheckboxItem, + ContextMenuRadioItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuGroup, + ContextMenuPortal, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuRadioGroup, +} diff --git a/src/lib/session.ts b/src/lib/session.ts index a688ede..9c649e2 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -25,4 +25,10 @@ export default class Session { public async addServer(s: ServerAPI) { return await axios.post("/api/servers", { ...s, sessionId: this.id }); } + + public async removeServer(index: number) { + return await axios.delete("/api/servers", { + params: { index, sessionId: this.id }, + }); + } }