diff --git a/.gitignore b/.gitignore index 5ef6a52..7be0664 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +sentryx/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 88f02f3..d5549f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,9 +23,11 @@ "lucide-react": "^0.525.0", "maplibre-gl": "^5.6.1", "next": "15.4.1", + "next-themes": "^0.4.6", "react": "19.1.0", "react-dom": "19.1.0", "recharts": "^2.15.4", + "sonner": "^2.0.6", "tailwind-merge": "^3.3.1" }, "devDependencies": { @@ -3067,6 +3069,16 @@ } } }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -3439,6 +3451,16 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/sonner": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.6.tgz", + "integrity": "sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/package.json b/package.json index 4eea989..4c04bcc 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,11 @@ "lucide-react": "^0.525.0", "maplibre-gl": "^5.6.1", "next": "15.4.1", + "next-themes": "^0.4.6", "react": "19.1.0", "react-dom": "19.1.0", "recharts": "^2.15.4", + "sonner": "^2.0.6", "tailwind-merge": "^3.3.1" }, "devDependencies": { diff --git a/sentryx/servers.json b/sentryx/servers.json index e2ac3a6..fe51488 100644 --- a/sentryx/servers.json +++ b/sentryx/servers.json @@ -1 +1 @@ -[{"name":"Main Server","ip":"10.7.1.2","coordinates":[19.8187,41.3275]},{"name":"Second Server","ip":"10.7.1.2","coordinates":[19.8187,42]},{"name":"test","ip":"abc","coordinates":[14.212747852350674,13.997221304482736]},{"name":"Bro","ip":"10.7.1.53","coordinates":[19.66455594156352,40.826754398461304]},{"name":"Idk lol","ip":"10.32.2.4","coordinates":[20.135507417925936,41.17975477202245]}] \ No newline at end of file +[] diff --git a/sentryx/sessions.json b/sentryx/sessions.json index 46e3549..ef98b76 100644 --- a/sentryx/sessions.json +++ b/sentryx/sessions.json @@ -1 +1 @@ -{"SESH-344d851e-01ea-4010-8c6a-6205b6772055":"admin","SESH-cc54eaab-a8b9-45d6-8be2-406e346581e8":"admin","SESH-909ab0b4-89e9-4a48-aa16-b8d257421651":"admin","SESH-a930805a-319b-4079-9f81-64a7026e271c":"admin","SESH-8902f651-201d-4493-b1f7-763512cff5b6":"admin"} \ No newline at end of file +{"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 index fe7614b..11306b5 100644 --- a/sentryx/users.json +++ b/sentryx/users.json @@ -1,6 +1 @@ -{ - "admin": { - "username": "admin", - "password": "admin" - } -} +{"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 d7c9246..97033fe 100644 --- a/src/app/api/auth/route.ts +++ b/src/app/api/auth/route.ts @@ -4,11 +4,11 @@ import Data, { findKey } from "../data"; const users = new Data<{ [key: string]: { username: string; password: string }; -}>("sentryx/users.json", "object"); +}>("sentryx/users.json", { admin: { username: "admin", password: "admin" } }); const sessions = new Data<{ [key: string]: string }>( "sentryx/sessions.json", - "object" + {} ); export async function POST(request: Request) { @@ -44,9 +44,9 @@ export async function POST(request: Request) { export async function GET(request: Request) { const { searchParams } = new URL(request.url); - const id = searchParams.get("id") || ""; + const sessionId = searchParams.get("sessionId") || ""; - if (!sessions.data[id]) + if (!sessions.data[sessionId]) return NextResponse.json( { message: "Invalid session" }, { status: HttpStatusCode.Unauthorized } @@ -54,3 +54,7 @@ export async function GET(request: Request) { return NextResponse.json({ message: "ok" }); } + +export default function validate(sessionId: string) { + return sessions.data[sessionId] != undefined; +} diff --git a/src/app/api/data.ts b/src/app/api/data.ts index 0ce0e7c..5697084 100644 --- a/src/app/api/data.ts +++ b/src/app/api/data.ts @@ -1,4 +1,5 @@ -import { existsSync, readFileSync, writeFileSync } from "fs"; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import { dirname } from "path"; export function findKey( obj: T, @@ -12,17 +13,17 @@ export function findKey( export default class Data { data: T; file_path: string; - constructor(file_path: string, type: "object" | "array") { + constructor(file_path: string, def: T) { this.file_path = file_path; if (existsSync(file_path)) { try { this.data = JSON.parse(readFileSync(file_path, "utf-8")); } catch { - this.data = (type == "object" ? {} : []) as T; + this.data = def; this.write(); } } else { - this.data = (type == "object" ? {} : []) as T; + this.data = def; this.write(); } } @@ -32,6 +33,11 @@ export default class Data { } public write() { + const dir = dirname(this.file_path); + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + writeFileSync(this.file_path, JSON.stringify(this.data)); } } diff --git a/src/app/api/servers/route.ts b/src/app/api/servers/route.ts index 2fd3340..b52d3c2 100644 --- a/src/app/api/servers/route.ts +++ b/src/app/api/servers/route.ts @@ -2,8 +2,9 @@ import { NextResponse } from "next/server"; import Data from "../data"; import Server, { ServerAPI } from "@/types/server"; import { HttpStatusCode } from "axios"; +import validate from "../auth/route"; -const servers = new Data("sentryx/servers.json", "array"); +const servers = new Data("sentryx/servers.json", []); export async function GET(request: Request) { const { searchParams } = new URL(request.url); @@ -44,10 +45,16 @@ export async function GET(request: Request) { export async function POST(request: Request) { const body = await request.json(); + if (!validate(body.sessionId)) + return NextResponse.json( + { message: "Invalid session" }, + { status: HttpStatusCode.Unauthorized } + ); + if (!body.name || !body.ip) return NextResponse.json( { - message: "Invalid body", + message: "The name or IP are invalid or empty", }, { status: HttpStatusCode.BadRequest, diff --git a/src/app/dashboard/server.tsx b/src/app/dashboard/server.tsx index 5c8e173..fca216f 100644 --- a/src/app/dashboard/server.tsx +++ b/src/app/dashboard/server.tsx @@ -8,7 +8,14 @@ import { import { Label } from "@/components/ui/label"; import { Progress } from "@/components/ui/progress"; import Server from "@/types/server"; -import { Cpu, HardDrive, MemoryStick, Network, Wifi } from "lucide-react"; +import { + Cpu, + HardDrive, + MapPin, + MemoryStick, + Network, + Wifi, +} from "lucide-react"; function UsageBar({ value, @@ -39,9 +46,17 @@ export default function ServerComponent({ server }: { server: Server }) { {server.name} - - {server.status[0].toUpperCase() + server.status.slice(1)} {" · "} - {server.ip} + +
+ {server.status[0].toUpperCase() + server.status.slice(1)} {" · "} + {server.ip} + {server.location && ( +
+ + {server.location} +
+ )} +
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b9682e4..14dfa8e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import "./globals.css"; import AppLayout from "@/components/AppLayout"; // import your wrapper +import { Toaster } from "@/components/ui/sonner"; export const metadata: Metadata = { title: "Sentryx Control Panel", @@ -16,6 +17,7 @@ export default function RootLayout({ {children} + ); diff --git a/src/app/servers/NewServerDialog.tsx b/src/app/servers/NewServerDialog.tsx index d59076d..3ea5ab4 100644 --- a/src/app/servers/NewServerDialog.tsx +++ b/src/app/servers/NewServerDialog.tsx @@ -15,6 +15,7 @@ import { Label } from "@/components/ui/label"; import MapSelect from "./LocationSelect"; import { useState } from "react"; import Session from "@/lib/session"; +import { toast } from "sonner"; export default function NewServerDialog({ session, @@ -24,7 +25,8 @@ export default function NewServerDialog({ const [open, setOpen] = useState(false); const [name, setName] = useState(""); const [ip, setIp] = useState(""); - const [location, setLocation] = useState<[number, number] | undefined>( + const [location, setLocation] = useState(undefined); + const [coordinates, setCoordinates] = useState<[number, number] | undefined>( undefined ); @@ -52,12 +54,33 @@ export default function NewServerDialog({ onInput={(e) => setIp(e.currentTarget.value)} /> + + + setLocation( + e.currentTarget.value == "" ? undefined : e.currentTarget.value + ) + } + /> + - +