Browse Source

dashboard servers

Leo dev 1 week ago
parent
commit
f78a76ebf0

+ 21 - 0
sentryx/servers.json

@@ -2,5 +2,26 @@
   {
     "name": "Main Server",
     "ip": "10.7.1.2"
+  },
+  {
+    "name": "Second Server",
+    "ip": "10.7.1.2"
+  },
+  {
+    "name": "Second Server",
+    "ip": "10.7.1.2"
+  },
+  {
+    "name": "Second Server",
+    "ip": "10.7.1.2"
+  },
+  {
+    "name": "Second Server",
+    "ip": "10.7.1.2"
+  },
+  {
+    "name": "Second Server",
+    "ip": "10.7.1.2"
   }
+
 ]

+ 1 - 0
sentryx/sessions.json

@@ -0,0 +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"}

+ 140 - 0
src/app/Server.css

@@ -0,0 +1,140 @@
+.server-container .server {
+  box-sizing: border-box;
+  background-color: var(--card-bg);
+  border-radius: 10px;
+  width: 100%;
+  height: 100%;
+  padding: 30px;
+}
+
+.server-heading-inner {
+  display: flex;
+  gap: 10px;
+}
+
+.server-icon {
+  padding: 0.5rem;
+  border-radius: 10px;
+  width: 20px;
+  height: 20px;
+  background-color: var(--server-icon-bg);
+  color: rgb(37 99 235);
+  margin-top: auto;
+  margin-bottom: auto;
+}
+
+.server-heading-info {
+  display: flex;
+  flex-direction: column;
+  gap: 7px;
+}
+
+.server-heading-info div div {
+  display: flex;
+  gap: 3px;
+  width: max-content;
+  padding-left: 10px;
+  padding-right: 10px;
+  padding-top: 3px;
+  padding-bottom: 3px;
+  border-radius: 10px;
+}
+
+.server-heading-info div div svg {
+  margin-top: auto;
+  margin-bottom: auto;
+}
+
+.server-heading-info div div p {
+  margin-top: auto;
+  margin-bottom: auto;
+  font-size: 14px;
+  color: currentColor;
+}
+
+.server-heading-info div {
+  display: flex;
+  flex-direction: row;
+  gap: 10px;
+}
+
+.server-heading-info div p {
+  margin: 0;
+  font-size: 14.5px;
+  margin-top: auto;
+  margin-bottom: auto;
+  color: rgb(100 116 139);
+}
+
+.server-heading {
+  display: flex;
+  justify-content: space-between;
+}
+
+.server-heading button {
+  margin: 0;
+  border: 0;
+  height: max-content;
+  width: max-content;
+  padding: 8px;
+  align-items: center;
+  background-color: transparent;
+  border-radius: 7px;
+  margin-top: auto;
+  margin-bottom: auto;
+  color: var(--fg);
+}
+
+.server-heading button:hover {
+  background-color: var(--bg);
+}
+
+.server-monitor h2 {
+  margin: 0;
+  padding: 0;
+  margin-bottom: 20px;
+  display: flex;
+  gap: 7px;
+}
+
+.server-monitor h2 svg {
+  margin-top: auto;
+  margin-bottom: auto;
+}
+
+.server-monitor div {
+  display: flex;
+  justify-content: space-between;
+}
+
+.server-monitor div p {
+  margin-bottom: 8px;
+  font-size: 14px;
+  display: flex;
+  gap: 6px;
+}
+
+.All {
+  color: var(--fg);
+  background-color: var(--bg);
+}
+
+.Online {
+  color: var(--online);
+  background-color: var(--online-bg);
+}
+
+.Offline {
+  color: var(--offline);
+  background-color: var(--offline-bg);
+}
+
+.Warning {
+  color: var(--warning);
+  background-color: var(--warning-bg);
+}
+
+.Maintenance {
+  color: var(--maintenance);
+  background-color: var(--maintenance-bg);
+}

+ 35 - 3
src/app/api/servers/route.ts

@@ -1,9 +1,41 @@
 import { NextResponse } from "next/server";
 import Data from "../data";
-import { ServerAPI } from "@/types/server";
+import Server, { ServerAPI } from "@/types/server";
 
-const servers = new Data<ServerAPI>("sentryx/servers.json", "array");
+const servers = new Data<ServerAPI[]>("sentryx/servers.json", "array");
 
 export async function GET(request: Request) {
-  return NextResponse.json({ message: "ok", servers: servers.data });
+  const { searchParams } = new URL(request.url);
+  const query = searchParams.get("s");
+  const currentServers = servers.data.map(
+    (s) =>
+      ({
+        ...s,
+        status: "online",
+        cpu: 20,
+        memory: 70,
+        storage: 40,
+        network: 30,
+      } as Server)
+  );
+
+  if (query) {
+    let tokens = (query || "").toLocaleLowerCase().split(" ");
+
+    console.log(tokens);
+
+    return NextResponse.json({
+      message: "ok",
+      servers: currentServers.filter((s) =>
+        tokens.some(
+          (t) => s.name.toLowerCase().includes(t) || s.status.includes(t)
+        )
+      ),
+    });
+  }
+
+  return NextResponse.json({
+    message: "ok",
+    servers: currentServers,
+  });
 }

+ 110 - 13
src/app/dashboard/page.tsx

@@ -6,6 +6,13 @@ import { cn } from "@/lib/utils";
 import { CircleCheckBig, Cpu, MemoryStick, TriangleAlert } from "lucide-react";
 import { ChartAreaGradient } from "./chart";
 import { Progress } from "@/components/ui/progress";
+import getSession from "@/hooks/getSession";
+import ServerComponent from "./server";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { useEffect, useState } from "react";
+import useSession from "@/hooks/useSession";
+import Server from "@/types/server";
 
 export function generateHourlyData() {
   const result = [];
@@ -60,43 +67,133 @@ export function Summary({
   );
 }
 
+function average<T>(values: Array<T>, p: (v: T) => number) {
+  return values.length > 0
+    ? values.reduce((sum, value) => sum + p(value), 0) / values.length
+    : 0;
+}
+
+const d = generateHourlyData();
+
 export default function Dashboard() {
-  // const session = await getSession();
-  const d = generateHourlyData();
+  const session = useSession();
+  const [servers, setServers] = useState<Server[]>([]);
+  const [searchedServers, setSearchedServers] = useState<Server[]>([]);
+  const [serversQuery, setServersQuery] = useState("");
+  const [serverData, setServerData] = useState({
+    online: 0,
+    issues: 0,
+    cpu: 0,
+    memory: 0,
+    storage: 0,
+    network: 0,
+  });
+
+  useEffect(() => {
+    if (!session) return;
+
+    session.getServers().then((servers) => {
+      setServers(servers);
+      setServerData({
+        online: servers.reduce(
+          (sum, server) => (server.status == "online" ? sum + 1 : sum),
+          0
+        ),
+        issues: servers.reduce(
+          (sum, server) => (server.status == "warning" ? sum + 1 : sum),
+          0
+        ),
+        cpu: average(servers, (s) => s.cpu),
+        memory: average(servers, (s) => s.memory),
+        storage: average(servers, (s) => s.storage),
+        network: average(servers, (s) => s.network),
+      });
+    });
+  }, [session]);
+
+  useEffect(() => {
+    const timeout = setTimeout(() => {
+      let tokens = serversQuery.toLowerCase().split(" ");
+      setSearchedServers(
+        servers.filter((s) =>
+          tokens.some(
+            (t) => s.name.toLowerCase().includes(t) || s.status.includes(t)
+          )
+        )
+      );
+    }, 30);
+
+    return () => clearTimeout(timeout);
+  }, [serversQuery, servers]);
+
   return (
     <div className="w-full flex flex-col gap-5">
       <div className="grid gap-4 w-full grid-cols-2 xl:grid-cols-4">
-        <SummaryCard className="text-chart-3" value="69" icon={CircleCheckBig}>
+        <SummaryCard
+          className="text-chart-3"
+          value={`${serverData.online}`}
+          icon={CircleCheckBig}
+        >
           <span className="hidden sm:inline">Online Servers</span>
           <span className="inline sm:hidden">Online</span>
         </SummaryCard>
-        <SummaryCard className="text-chart-4" value="2" icon={TriangleAlert}>
+        <SummaryCard
+          className="text-chart-4"
+          value={`${serverData.issues}`}
+          icon={TriangleAlert}
+        >
           Issues
         </SummaryCard>
-        <SummaryCard className="text-chart-2" value="52%" icon={Cpu}>
+        <SummaryCard
+          className="text-chart-2"
+          value={`${serverData.cpu}%`}
+          icon={Cpu}
+        >
           <span className="hidden sm:inline">Avg CPU Usage</span>
           <span className="inline sm:hidden">CPU</span>
         </SummaryCard>
-        <SummaryCard className="text-chart-1" value="100%" icon={MemoryStick}>
+        <SummaryCard
+          className="text-chart-1"
+          value={`${serverData.memory}%`}
+          icon={MemoryStick}
+        >
           <span className="hidden sm:inline">Avg Memory Usage</span>
           <span className="inline sm:hidden">Memory</span>
         </SummaryCard>
       </div>
 
-      <div className="w-full h-96 flex gap-5 flex-col md:flex-row">
-        <ChartAreaGradient chartData={d} />
-        <Card>
+      <div className="w-full flex gap-5 flex-col md:flex-row">
+        <div className="h-96 w-full">
+          <ChartAreaGradient chartData={d} />
+        </div>
+        <Card className="h-96">
           <CardHeader>
             <CardTitle>System Overview</CardTitle>
           </CardHeader>
           <CardContent className="flex flex-col gap-6 w-full md:w-96">
-            <Summary value={20}>Average CPU</Summary>
-            <Summary value={69}>Average Memory</Summary>
-            <Summary value={69}>Storage</Summary>
-            <Summary value={69}>Average Network</Summary>
+            <Summary value={serverData.cpu}>Average CPU</Summary>
+            <Summary value={serverData.memory}>Average Memory</Summary>
+            <Summary value={serverData.storage}>Storage</Summary>
+            <Summary value={serverData.network}>Average Network</Summary>
           </CardContent>
         </Card>
       </div>
+
+      <Card>
+        <CardContent className="flex flex-row gap-3">
+          <Input
+            onInput={(e) => setServersQuery(e.currentTarget.value)}
+            placeholder="Search servers by name, location or IP..."
+          />
+          <Button onClick={() => {}}>Search</Button>
+        </CardContent>
+      </Card>
+
+      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
+        {searchedServers.map((s, i) => (
+          <ServerComponent server={s} key={i} />
+        ))}
+      </div>
     </div>
   );
 }

+ 75 - 0
src/app/dashboard/server.tsx

@@ -0,0 +1,75 @@
+import {
+  Card,
+  CardContent,
+  CardDescription,
+  CardHeader,
+  CardTitle,
+} from "@/components/ui/card";
+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";
+
+function UsageBar({
+  value,
+  Icon,
+  text,
+  color,
+}: {
+  value: number;
+  Icon: React.ElementType;
+  text: string;
+  color: string;
+}) {
+  return (
+    <div className="flex flex-col gap-2 w-full">
+      <div className="justify-between flex">
+        <Label className="text-md flex justify-center">
+          <Icon size={16} color={color} /> {text}
+        </Label>
+        <Label className="text-md">{value}%</Label>
+      </div>
+      <Progress value={value} />
+    </div>
+  );
+}
+
+export default function ServerComponent({ server }: { server: Server }) {
+  return (
+    <Card className="w-full transition-transform duration-500 hover:scale-102 hover:cursor-pointer">
+      <CardHeader>
+        <CardTitle>{server.name}</CardTitle>
+        <CardDescription>
+          {server.status[0].toUpperCase() + server.status.slice(1)} {" · "}
+          {server.ip}
+        </CardDescription>
+      </CardHeader>
+      <CardContent className="w-full flex flex-col gap-3.5">
+        <UsageBar
+          value={server.cpu}
+          Icon={Cpu}
+          text="CPU"
+          color="var(--chart-2)"
+        />
+        <UsageBar
+          value={server.memory}
+          Icon={MemoryStick}
+          text="Memory"
+          color="var(--chart-1)"
+        />
+        <UsageBar
+          value={server.storage}
+          Icon={HardDrive}
+          text="Storage"
+          color="var(--chart-5)"
+        />
+        <UsageBar
+          value={server.network}
+          Icon={Network}
+          text="Network"
+          color="var(--chart-3)"
+        />
+      </CardContent>
+    </Card>
+  );
+}

+ 1 - 1
src/components/AppLayout.tsx

@@ -17,7 +17,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
         <AppSidebar />
         <main className="flex-1 flex flex-col min-h-0">
           <SidebarTrigger />
-          <div className="px-4 lg:px-10 flex-1 min-h-0">{children}</div>
+          <div className="px-4 lg:px-10 flex-1 min-h-0 mb-6">{children}</div>
         </main>
       </div>
     </SidebarProvider>

+ 1 - 1
src/hooks/useSession.ts

@@ -24,7 +24,7 @@ export default function useSession() {
           console.log(e);
           router.replace("/login");
         });
-  });
+  }, []);
 
   return session;
 }

+ 17 - 0
src/lib/session.ts

@@ -1,7 +1,24 @@
+import Server from "@/types/server";
+import axios from "axios";
+
 export default class Session {
   id: string;
 
   constructor(id: string) {
     this.id = id;
   }
+
+  public async getServers(query?: string): Promise<Server[]> {
+    try {
+      return (
+        ((
+          await axios.get(`/api/servers?s=${query || ""}`, {
+            params: { id: this.id },
+          })
+        ).data.servers as Server[]) || []
+      );
+    } catch {
+      return [];
+    }
+  }
 }

+ 4 - 0
src/types/server.ts

@@ -5,4 +5,8 @@ export interface ServerAPI {
 
 export default interface Server extends ServerAPI {
   status: "online" | "offline" | "warning" | "maintenance";
+  cpu: number;
+  memory: number;
+  storage: number;
+  network: number;
 }