refactor(serverlist): refactored to also show uptime and other info about the server
This commit is contained in:
@@ -16,11 +16,11 @@ headers {
|
|||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"name": "Bowling Green 2",
|
"name": "Bowling Green 1",
|
||||||
"serverDNS": "USBOW2VMS006",
|
"serverDNS": "USBOW1VS006",
|
||||||
"plantToken": "usbow2",
|
"plantToken": "usbow1",
|
||||||
"ipAddress": "10.30.0.26",
|
"ipAddress": "10.25.0.26",
|
||||||
"greatPlainsPlantCode": 0,
|
"greatPlainsPlantCode": 55,
|
||||||
"lstServerPort": 4000,
|
"lstServerPort": 4000,
|
||||||
"serverLoc": "E$\\LST"
|
"serverLoc": "E$\\LST"
|
||||||
}
|
}
|
||||||
@@ -28,4 +28,5 @@ body:json {
|
|||||||
|
|
||||||
settings {
|
settings {
|
||||||
encodeUrl: true
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: All Server Stats
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{url}}/lst/api/system/allservers/stats
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ const registerSchema = z.object({
|
|||||||
.string()
|
.string()
|
||||||
.min(3)
|
.min(3)
|
||||||
.max(32)
|
.max(32)
|
||||||
.regex(/^[a-zA-Z0-9_]+$/, "Only alphanumeric + underscores"),
|
.regex(/^[a-zA-Z0-9.]+$/, "Only alphanumeric + dots allowed"),
|
||||||
displayUsername: z.string().min(2).max(100).optional(), // optional in your API, but supported
|
displayUsername: z.string().min(2).max(100).optional(), // optional in your API, but supported
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import type { Express, Request, Response } from "express";
|
import type { Express, Request, Response } from "express";
|
||||||
|
import allServerStats from "./routes/allServerStats.js";
|
||||||
import modules from "./routes/modules/moduleRoutes.js";
|
import modules from "./routes/modules/moduleRoutes.js";
|
||||||
import settings from "./routes/settings/settingRoutes.js";
|
import settings from "./routes/settings/settingRoutes.js";
|
||||||
import stats from "./routes/stats.js";
|
import stats from "./routes/stats.js";
|
||||||
|
|
||||||
export const setupSystemRoutes = (app: Express, basePath: string) => {
|
export const setupSystemRoutes = (app: Express, basePath: string) => {
|
||||||
app.use(basePath + "/api/system/stats", stats);
|
app.use(basePath + "/api/system/stats", stats);
|
||||||
|
app.use(basePath + "/api/system/allservers/stats", allServerStats);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
basePath + "/api/system/settings", // will pass bc system admin but this is just telling us we need this
|
basePath + "/api/system/settings", // will pass bc system admin but this is just telling us we need this
|
||||||
|
|||||||
90
app/src/internal/system/routes/allServerStats.ts
Normal file
90
app/src/internal/system/routes/allServerStats.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { format } from "date-fns-tz";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { Router } from "express";
|
||||||
|
import { db } from "../../../pkg/db/db.js";
|
||||||
|
import { serverData } from "../../../pkg/db/schema/servers.js";
|
||||||
|
import {
|
||||||
|
type ServerStats,
|
||||||
|
serverStats,
|
||||||
|
} from "../../../pkg/db/schema/serverstats.js";
|
||||||
|
import dbTransport from "../../../pkg/logger/dbTransport.js";
|
||||||
|
import { tryCatch } from "../../../pkg/utils/tryCatch.js";
|
||||||
|
import { checkBuildUpdate } from "../utlis/checkForBuild.js";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
type ServerInfo = {
|
||||||
|
server: string;
|
||||||
|
};
|
||||||
|
// GET /health
|
||||||
|
router.get("/", async (req, res) => {
|
||||||
|
const { data, error } = await tryCatch(
|
||||||
|
db.select().from(serverStats).where(eq(serverStats.id, "serverStats")),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
res.status(400).json({ error: error });
|
||||||
|
}
|
||||||
|
|
||||||
|
const statData = data as ServerStats[];
|
||||||
|
const used = process.memoryUsage();
|
||||||
|
|
||||||
|
const serverInfo = [
|
||||||
|
{
|
||||||
|
server: "Dev Server",
|
||||||
|
status: "ok",
|
||||||
|
plantToken: "test3",
|
||||||
|
uptime: process.uptime(),
|
||||||
|
build: statData[0]?.build,
|
||||||
|
pendingUpdateFile: await checkBuildUpdate(["."]),
|
||||||
|
lastUpdate: statData[0]?.lastUpdate
|
||||||
|
? format(statData[0].lastUpdate, "MM/dd/yyyy HH:mm")
|
||||||
|
: "",
|
||||||
|
memoryUsage: `Heap: ${(used.heapUsed / 1024 / 1024).toFixed(2)} MB / RSS: ${(
|
||||||
|
used.rss / 1024 / 1024
|
||||||
|
).toFixed(2)} MB`,
|
||||||
|
},
|
||||||
|
] as any;
|
||||||
|
// const { data: serverInfo } = await axios.get(
|
||||||
|
// `https://ushou1prod.alpla.net/lst/api/system/stats`,
|
||||||
|
// );
|
||||||
|
// get the server list so we can go to each and get there stats
|
||||||
|
const { data: sInfo, error: eInfo } = (await tryCatch(
|
||||||
|
db.select().from(serverData),
|
||||||
|
)) as any;
|
||||||
|
|
||||||
|
if (eInfo) {
|
||||||
|
console.log("There was an error getting the serverData");
|
||||||
|
}
|
||||||
|
for (let i = 0; i < sInfo.length; i++) {
|
||||||
|
//console.log(`Getting stats for ${sInfo[i].plantToken}`);
|
||||||
|
try {
|
||||||
|
let url = "";
|
||||||
|
if (sInfo[i].plantToken.includes("test")) {
|
||||||
|
url = `https://${sInfo[i].serverDNS}.alpla.net/lst/api/system/stats`;
|
||||||
|
} else {
|
||||||
|
url = `https://${sInfo[i].plantToken}prod.alpla.net/lst/api/system/stats`;
|
||||||
|
}
|
||||||
|
const { data: prodServerInfo } = await axios.get(url);
|
||||||
|
|
||||||
|
// console.log(
|
||||||
|
// `${sInfo[i].plantToken}, data ${JSON.stringify(prodServerInfo)}`,
|
||||||
|
// );
|
||||||
|
serverInfo.push({
|
||||||
|
server: sInfo[i].name,
|
||||||
|
status: prodServerInfo.status,
|
||||||
|
plantToken: sInfo[i].plantToken,
|
||||||
|
uptime: prodServerInfo.uptime,
|
||||||
|
build: prodServerInfo.build,
|
||||||
|
pendingUpdateFile: prodServerInfo.pendingUpdateFile,
|
||||||
|
lastUpdate: prodServerInfo.lastUpdate,
|
||||||
|
memoryUsage: prodServerInfo.memoryUsage,
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(serverInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -3,7 +3,7 @@ import axios from "axios";
|
|||||||
|
|
||||||
export function getServers() {
|
export function getServers() {
|
||||||
return queryOptions({
|
return queryOptions({
|
||||||
queryKey: ["getModules"],
|
queryKey: ["getServers"],
|
||||||
queryFn: () => fetchSession(),
|
queryFn: () => fetchSession(),
|
||||||
staleTime: 5000,
|
staleTime: 5000,
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ export function getServerStats() {
|
|||||||
return queryOptions({
|
return queryOptions({
|
||||||
queryKey: ["getServerStats"],
|
queryKey: ["getServerStats"],
|
||||||
queryFn: () => fetchSession(),
|
queryFn: () => fetchSession(),
|
||||||
staleTime: 5000,
|
staleTime: 2 * 60 * 1000,
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchSession = async () => {
|
const fetchSession = async () => {
|
||||||
const { data } = await axios.get(`/lst/api/system/stats`);
|
const { data } = await axios.get(`/lst/api/system/allservers/stats`);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -24,6 +25,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { getServers } from "@/lib/querys/admin/getServers";
|
import { getServers } from "@/lib/querys/admin/getServers";
|
||||||
|
import { getServerStats } from "@/lib/querys/serverStats";
|
||||||
|
|
||||||
type ServerData = {
|
type ServerData = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -34,6 +36,7 @@ type ServerData = {
|
|||||||
build: string;
|
build: string;
|
||||||
pendingUpdateFile: string;
|
pendingUpdateFile: string;
|
||||||
lastUpdate: Date;
|
lastUpdate: Date;
|
||||||
|
memoryUsage: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateServerItem = async (
|
const updateServerItem = async (
|
||||||
@@ -58,14 +61,18 @@ export const Route = createFileRoute("/_app/_adminLayout/admin/servers")({
|
|||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { data, isLoading, refetch } = useQuery(getServers());
|
const { data, isLoading, refetch } = useQuery(getServers());
|
||||||
// const {
|
const { data: stats = [], isLoading: loadingStats } = useQuery(
|
||||||
// data: stats,
|
getServerStats(),
|
||||||
// isLoading: loadingStats,
|
);
|
||||||
// refetch: statsRefetch,
|
|
||||||
// } = useQuery(getServerStats());
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const columnHelper = createColumnHelper<ServerData>();
|
const columnHelper = createColumnHelper<ServerData>();
|
||||||
|
|
||||||
|
const statsMap = React.useMemo(() => {
|
||||||
|
const map = new Map<string, any>();
|
||||||
|
stats.forEach((stat: any) => map.set(stat.plantToken, stat));
|
||||||
|
return map;
|
||||||
|
}, [stats]);
|
||||||
|
|
||||||
const updateServer = useMutation({
|
const updateServer = useMutation({
|
||||||
mutationFn: ({
|
mutationFn: ({
|
||||||
token,
|
token,
|
||||||
@@ -197,15 +204,81 @@ function RouteComponent() {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// columnHelper.accessor("uptime", {
|
columnHelper.accessor("uptime", {
|
||||||
// header: () => {
|
header: () => {
|
||||||
// return <span className="flex flex-row gap-2">UpTime</span>;
|
return <span className="flex flex-row gap-2">UpTime</span>;
|
||||||
// },
|
},
|
||||||
// cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
// //console.log(data);
|
const statsRow = statsMap.get(row.original.plantToken);
|
||||||
// return <span>{data?.uptime ?? "--"}</span>;
|
|
||||||
// },
|
if (loadingStats) {
|
||||||
// }),
|
// use Skeleton from shadcn/ui (or any placeholder)
|
||||||
|
return <Skeleton className="h-4 w-16" />;
|
||||||
|
}
|
||||||
|
console.log(statsMap);
|
||||||
|
return <span>{statsRow ? `${statsRow.uptime.toFixed(2)}` : "--"}</span>;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("lastUpdate", {
|
||||||
|
header: () => {
|
||||||
|
return <span className="flex flex-row gap-2">Last Updated</span>;
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const statsRow = statsMap.get(row.original.plantToken);
|
||||||
|
|
||||||
|
if (loadingStats) {
|
||||||
|
// use Skeleton from shadcn/ui (or any placeholder)
|
||||||
|
return <Skeleton className="h-4 w-16" />;
|
||||||
|
}
|
||||||
|
console.log(statsMap);
|
||||||
|
return <span>{statsRow ? `${statsRow.lastUpdate}` : "--"}</span>;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("build", {
|
||||||
|
header: () => {
|
||||||
|
return <span className="flex flex-row gap-2">Build Number</span>;
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const statsRow = statsMap.get(row.original.plantToken);
|
||||||
|
|
||||||
|
if (loadingStats) {
|
||||||
|
// use Skeleton from shadcn/ui (or any placeholder)
|
||||||
|
return <Skeleton className="h-4 w-16" />;
|
||||||
|
}
|
||||||
|
console.log(statsMap);
|
||||||
|
return <span>{statsRow ? `${statsRow.build}` : "--"}</span>;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("pendingUpdateFile", {
|
||||||
|
header: () => {
|
||||||
|
return <span className="flex flex-row gap-2">Pending Update</span>;
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const statsRow = statsMap.get(row.original.plantToken);
|
||||||
|
|
||||||
|
if (loadingStats) {
|
||||||
|
// use Skeleton from shadcn/ui (or any placeholder)
|
||||||
|
return <Skeleton className="h-4 w-16" />;
|
||||||
|
}
|
||||||
|
console.log(statsMap);
|
||||||
|
return <span>{statsRow ? `${statsRow.pendingUpdateFile}` : "--"}</span>;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("memoryUsage", {
|
||||||
|
header: () => {
|
||||||
|
return <span className="flex flex-row gap-2">Memory Usage</span>;
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const statsRow = statsMap.get(row.original.plantToken);
|
||||||
|
|
||||||
|
if (loadingStats) {
|
||||||
|
// use Skeleton from shadcn/ui (or any placeholder)
|
||||||
|
return <Skeleton className="h-4 w-16" />;
|
||||||
|
}
|
||||||
|
//console.log(statsMap);
|
||||||
|
return <span>{statsRow ? `${statsRow.memoryUsage}` : "--"}</span>;
|
||||||
|
},
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
|
|||||||
Reference in New Issue
Block a user