refactor(serverlist): refactored to also show uptime and other info about the server

This commit is contained in:
2025-10-31 07:14:11 -05:00
parent d60c08a281
commit e1e659f9b1
8 changed files with 205 additions and 23 deletions

View File

@@ -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
} }

View File

@@ -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
}

View File

@@ -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
}); });

View File

@@ -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

View 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;

View File

@@ -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,

View File

@@ -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;
}; };

View File

@@ -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({