This commit is contained in:
2025-07-17 05:51:15 +02:00
parent c3fb1c6cea
commit ebb71deee3
16 changed files with 112 additions and 42 deletions
+2
View File
@@ -39,3 +39,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
sentryx/
+22
View File
@@ -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",
+2
View File
@@ -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": {
+1 -1
View File
@@ -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]}]
[]
+1 -1
View File
@@ -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"}
{"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"}
+1 -6
View File
@@ -1,6 +1 @@
{
"admin": {
"username": "admin",
"password": "admin"
}
}
{"admin":{"username":"admin","password":"admin"}}
+8 -4
View File
@@ -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;
}
+10 -4
View File
@@ -1,4 +1,5 @@
import { existsSync, readFileSync, writeFileSync } from "fs";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname } from "path";
export function findKey<T extends object>(
obj: T,
@@ -12,17 +13,17 @@ export function findKey<T extends object>(
export default class Data<T> {
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<T> {
}
public write() {
const dir = dirname(this.file_path);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(this.file_path, JSON.stringify(this.data));
}
}
+9 -2
View File
@@ -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<ServerAPI[]>("sentryx/servers.json", "array");
const servers = new Data<ServerAPI[]>("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,
+17 -2
View File
@@ -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 }) {
<Card className="w-full transition-transform duration-500 hover:scale-102 hover:cursor-pointer">
<CardHeader>
<CardTitle>{server.name}</CardTitle>
<CardDescription>
<CardDescription className="flex justify-between">
<div className="flex gap-2">
{server.status[0].toUpperCase() + server.status.slice(1)} {" · "}
{server.ip}
{server.location && (
<div className="flex items-center gap-0.5">
<MapPin size={14} />
{server.location}
</div>
)}
</div>
</CardDescription>
</CardHeader>
<CardContent className="w-full flex flex-col gap-3.5">
+2
View File
@@ -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({
<html lang="en">
<body className="antialiased dark">
<AppLayout>{children}</AppLayout>
<Toaster />
</body>
</html>
);
+26 -3
View File
@@ -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<string | undefined>(undefined);
const [coordinates, setCoordinates] = useState<[number, number] | undefined>(
undefined
);
@@ -52,12 +54,33 @@ export default function NewServerDialog({
onInput={(e) => setIp(e.currentTarget.value)}
/>
<Label className="mt-2">Server Location Name</Label>
<Input
placeholder="Home, Warehouse 1, 4th ave, etc"
onInput={(e) =>
setLocation(
e.currentTarget.value == "" ? undefined : e.currentTarget.value
)
}
/>
<Label className="mt-2">Server Location</Label>
<MapSelect setLocation={setLocation} />
<MapSelect setLocation={setCoordinates} />
<DialogFooter>
<Button
onClick={() => {
session?.addServer({ name, ip, coordinates: location });
session
?.addServer({ name, ip, coordinates, location })
.then(() => {
toast.success(
"Server added successfully, refresh to view it"
);
})
.catch((e) => {
toast.error(
"Error adding server: " + e.response.data.message
);
});
setOpen(false);
}}
>
+1 -1
View File
@@ -14,7 +14,7 @@ export default async function getSession(): Promise<Session | null> {
try {
await axios.get("http://localhost:3000/api/auth", {
params: { id: sessionId },
params: { sessionId },
});
return new Session(sessionId);
+5 -8
View File
@@ -9,16 +9,13 @@ export default function useSession() {
const [session, setSession] = useState<Session | null>(null);
useEffect(() => {
const id = Cookies.get("session_id");
const sessionId = Cookies.get("session_id");
if (id)
axios({
method: "get",
url: "/api/auth",
params: { id },
})
if (sessionId)
axios
.get("/api/auth", { params: { sessionId } })
.then((r) => {
setSession(new Session(id));
setSession(new Session(sessionId));
})
.catch((e) => {
console.log(e);
+2 -8
View File
@@ -13,7 +13,7 @@ export default class Session {
return (
((
await axios.get(`/api/servers?s=${query || ""}`, {
params: { id: this.id },
params: { sessionId: this.id },
})
).data.servers as Server[]) || []
);
@@ -23,12 +23,6 @@ export default class Session {
}
public async addServer(s: ServerAPI) {
try {
return (
((await axios.post("/api/servers", s)).data.servers as Server[]) || []
);
} catch {
return [];
}
return await axios.post("/api/servers", { ...s, sessionId: this.id });
}
}
+1
View File
@@ -1,6 +1,7 @@
export interface ServerAPI {
name: string;
ip: string;
location?: string;
coordinates?: [number, number];
}