toast
This commit is contained in:
@@ -39,3 +39,5 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
sentryx/
|
||||||
Generated
+22
@@ -23,9 +23,11 @@
|
|||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"maplibre-gl": "^5.6.1",
|
"maplibre-gl": "^5.6.1",
|
||||||
"next": "15.4.1",
|
"next": "15.4.1",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
|
"sonner": "^2.0.6",
|
||||||
"tailwind-merge": "^3.3.1"
|
"tailwind-merge": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
@@ -3439,6 +3451,16 @@
|
|||||||
"is-arrayish": "^0.3.1"
|
"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": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
|||||||
@@ -24,9 +24,11 @@
|
|||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"maplibre-gl": "^5.6.1",
|
"maplibre-gl": "^5.6.1",
|
||||||
"next": "15.4.1",
|
"next": "15.4.1",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
|
"sonner": "^2.0.6",
|
||||||
"tailwind-merge": "^3.3.1"
|
"tailwind-merge": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
{"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
@@ -1,6 +1 @@
|
|||||||
{
|
{"admin":{"username":"admin","password":"admin"}}
|
||||||
"admin": {
|
|
||||||
"username": "admin",
|
|
||||||
"password": "admin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,11 +4,11 @@ import Data, { findKey } from "../data";
|
|||||||
|
|
||||||
const users = new Data<{
|
const users = new Data<{
|
||||||
[key: string]: { username: string; password: string };
|
[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 }>(
|
const sessions = new Data<{ [key: string]: string }>(
|
||||||
"sentryx/sessions.json",
|
"sentryx/sessions.json",
|
||||||
"object"
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
@@ -44,9 +44,9 @@ export async function POST(request: Request) {
|
|||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
const { searchParams } = new URL(request.url);
|
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(
|
return NextResponse.json(
|
||||||
{ message: "Invalid session" },
|
{ message: "Invalid session" },
|
||||||
{ status: HttpStatusCode.Unauthorized }
|
{ status: HttpStatusCode.Unauthorized }
|
||||||
@@ -54,3 +54,7 @@ export async function GET(request: Request) {
|
|||||||
|
|
||||||
return NextResponse.json({ message: "ok" });
|
return NextResponse.json({ message: "ok" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function validate(sessionId: string) {
|
||||||
|
return sessions.data[sessionId] != undefined;
|
||||||
|
}
|
||||||
|
|||||||
+10
-4
@@ -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>(
|
export function findKey<T extends object>(
|
||||||
obj: T,
|
obj: T,
|
||||||
@@ -12,17 +13,17 @@ export function findKey<T extends object>(
|
|||||||
export default class Data<T> {
|
export default class Data<T> {
|
||||||
data: T;
|
data: T;
|
||||||
file_path: string;
|
file_path: string;
|
||||||
constructor(file_path: string, type: "object" | "array") {
|
constructor(file_path: string, def: T) {
|
||||||
this.file_path = file_path;
|
this.file_path = file_path;
|
||||||
if (existsSync(file_path)) {
|
if (existsSync(file_path)) {
|
||||||
try {
|
try {
|
||||||
this.data = JSON.parse(readFileSync(file_path, "utf-8"));
|
this.data = JSON.parse(readFileSync(file_path, "utf-8"));
|
||||||
} catch {
|
} catch {
|
||||||
this.data = (type == "object" ? {} : []) as T;
|
this.data = def;
|
||||||
this.write();
|
this.write();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.data = (type == "object" ? {} : []) as T;
|
this.data = def;
|
||||||
this.write();
|
this.write();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,6 +33,11 @@ export default class Data<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public write() {
|
public write() {
|
||||||
|
const dir = dirname(this.file_path);
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
writeFileSync(this.file_path, JSON.stringify(this.data));
|
writeFileSync(this.file_path, JSON.stringify(this.data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { NextResponse } from "next/server";
|
|||||||
import Data from "../data";
|
import Data from "../data";
|
||||||
import Server, { ServerAPI } from "@/types/server";
|
import Server, { ServerAPI } from "@/types/server";
|
||||||
import { HttpStatusCode } from "axios";
|
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) {
|
export async function GET(request: Request) {
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
@@ -44,10 +45,16 @@ export async function GET(request: Request) {
|
|||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|
||||||
|
if (!validate(body.sessionId))
|
||||||
|
return NextResponse.json(
|
||||||
|
{ message: "Invalid session" },
|
||||||
|
{ status: HttpStatusCode.Unauthorized }
|
||||||
|
);
|
||||||
|
|
||||||
if (!body.name || !body.ip)
|
if (!body.name || !body.ip)
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
message: "Invalid body",
|
message: "The name or IP are invalid or empty",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: HttpStatusCode.BadRequest,
|
status: HttpStatusCode.BadRequest,
|
||||||
|
|||||||
@@ -8,7 +8,14 @@ import {
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import Server from "@/types/server";
|
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({
|
function UsageBar({
|
||||||
value,
|
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">
|
<Card className="w-full transition-transform duration-500 hover:scale-102 hover:cursor-pointer">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{server.name}</CardTitle>
|
<CardTitle>{server.name}</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription className="flex justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
{server.status[0].toUpperCase() + server.status.slice(1)} {" · "}
|
{server.status[0].toUpperCase() + server.status.slice(1)} {" · "}
|
||||||
{server.ip}
|
{server.ip}
|
||||||
|
{server.location && (
|
||||||
|
<div className="flex items-center gap-0.5">
|
||||||
|
<MapPin size={14} />
|
||||||
|
{server.location}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="w-full flex flex-col gap-3.5">
|
<CardContent className="w-full flex flex-col gap-3.5">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import AppLayout from "@/components/AppLayout"; // import your wrapper
|
import AppLayout from "@/components/AppLayout"; // import your wrapper
|
||||||
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Sentryx Control Panel",
|
title: "Sentryx Control Panel",
|
||||||
@@ -16,6 +17,7 @@ export default function RootLayout({
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className="antialiased dark">
|
<body className="antialiased dark">
|
||||||
<AppLayout>{children}</AppLayout>
|
<AppLayout>{children}</AppLayout>
|
||||||
|
<Toaster />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import MapSelect from "./LocationSelect";
|
import MapSelect from "./LocationSelect";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Session from "@/lib/session";
|
import Session from "@/lib/session";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export default function NewServerDialog({
|
export default function NewServerDialog({
|
||||||
session,
|
session,
|
||||||
@@ -24,7 +25,8 @@ export default function NewServerDialog({
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [ip, setIp] = 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
|
undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -52,12 +54,33 @@ export default function NewServerDialog({
|
|||||||
onInput={(e) => setIp(e.currentTarget.value)}
|
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>
|
<Label className="mt-2">Server Location</Label>
|
||||||
<MapSelect setLocation={setLocation} />
|
<MapSelect setLocation={setCoordinates} />
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
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);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default async function getSession(): Promise<Session | null> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.get("http://localhost:3000/api/auth", {
|
await axios.get("http://localhost:3000/api/auth", {
|
||||||
params: { id: sessionId },
|
params: { sessionId },
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Session(sessionId);
|
return new Session(sessionId);
|
||||||
|
|||||||
@@ -9,16 +9,13 @@ export default function useSession() {
|
|||||||
const [session, setSession] = useState<Session | null>(null);
|
const [session, setSession] = useState<Session | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = Cookies.get("session_id");
|
const sessionId = Cookies.get("session_id");
|
||||||
|
|
||||||
if (id)
|
if (sessionId)
|
||||||
axios({
|
axios
|
||||||
method: "get",
|
.get("/api/auth", { params: { sessionId } })
|
||||||
url: "/api/auth",
|
|
||||||
params: { id },
|
|
||||||
})
|
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
setSession(new Session(id));
|
setSession(new Session(sessionId));
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|||||||
+2
-8
@@ -13,7 +13,7 @@ export default class Session {
|
|||||||
return (
|
return (
|
||||||
((
|
((
|
||||||
await axios.get(`/api/servers?s=${query || ""}`, {
|
await axios.get(`/api/servers?s=${query || ""}`, {
|
||||||
params: { id: this.id },
|
params: { sessionId: this.id },
|
||||||
})
|
})
|
||||||
).data.servers as Server[]) || []
|
).data.servers as Server[]) || []
|
||||||
);
|
);
|
||||||
@@ -23,12 +23,6 @@ export default class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async addServer(s: ServerAPI) {
|
public async addServer(s: ServerAPI) {
|
||||||
try {
|
return await axios.post("/api/servers", { ...s, sessionId: this.id });
|
||||||
return (
|
|
||||||
((await axios.post("/api/servers", s)).data.servers as Server[]) || []
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export interface ServerAPI {
|
export interface ServerAPI {
|
||||||
name: string;
|
name: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
|
location?: string;
|
||||||
coordinates?: [number, number];
|
coordinates?: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user