diff --git a/LogisticsSupportTool_API_DOCS/app/admin/server/Add Server.bru b/LogisticsSupportTool_API_DOCS/app/admin/server/Add Server.bru index c68517e..f177dbc 100644 --- a/LogisticsSupportTool_API_DOCS/app/admin/server/Add Server.bru +++ b/LogisticsSupportTool_API_DOCS/app/admin/server/Add Server.bru @@ -16,11 +16,11 @@ headers { body:json { { - "name": "Bowling Green 2", - "serverDNS": "USBOW2VMS006", - "plantToken": "usbow2", - "ipAddress": "10.30.0.26", - "greatPlainsPlantCode": 0, + "name": "Bowling Green 1", + "serverDNS": "USBOW1VS006", + "plantToken": "usbow1", + "ipAddress": "10.25.0.26", + "greatPlainsPlantCode": 55, "lstServerPort": 4000, "serverLoc": "E$\\LST" } @@ -28,4 +28,5 @@ body:json { settings { encodeUrl: true + timeout: 0 } diff --git a/LogisticsSupportTool_API_DOCS/app/system/All Server Stats.bru b/LogisticsSupportTool_API_DOCS/app/system/All Server Stats.bru new file mode 100644 index 0000000..68aadf9 --- /dev/null +++ b/LogisticsSupportTool_API_DOCS/app/system/All Server Stats.bru @@ -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 +} diff --git a/app/src/internal/auth/routes/register.ts b/app/src/internal/auth/routes/register.ts index 775b208..fd13f00 100644 --- a/app/src/internal/auth/routes/register.ts +++ b/app/src/internal/auth/routes/register.ts @@ -18,7 +18,7 @@ const registerSchema = z.object({ .string() .min(3) .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 }); diff --git a/app/src/internal/system/routes.ts b/app/src/internal/system/routes.ts index 5c008de..50d3ee6 100644 --- a/app/src/internal/system/routes.ts +++ b/app/src/internal/system/routes.ts @@ -1,10 +1,12 @@ import type { Express, Request, Response } from "express"; +import allServerStats from "./routes/allServerStats.js"; import modules from "./routes/modules/moduleRoutes.js"; import settings from "./routes/settings/settingRoutes.js"; import stats from "./routes/stats.js"; export const setupSystemRoutes = (app: Express, basePath: string) => { app.use(basePath + "/api/system/stats", stats); + app.use(basePath + "/api/system/allservers/stats", allServerStats); app.use( basePath + "/api/system/settings", // will pass bc system admin but this is just telling us we need this diff --git a/app/src/internal/system/routes/allServerStats.ts b/app/src/internal/system/routes/allServerStats.ts new file mode 100644 index 0000000..afa5fb6 --- /dev/null +++ b/app/src/internal/system/routes/allServerStats.ts @@ -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; diff --git a/frontend/src/lib/querys/admin/getServers.ts b/frontend/src/lib/querys/admin/getServers.ts index 635a9df..379d9e2 100644 --- a/frontend/src/lib/querys/admin/getServers.ts +++ b/frontend/src/lib/querys/admin/getServers.ts @@ -3,7 +3,7 @@ import axios from "axios"; export function getServers() { return queryOptions({ - queryKey: ["getModules"], + queryKey: ["getServers"], queryFn: () => fetchSession(), staleTime: 5000, refetchOnWindowFocus: true, diff --git a/frontend/src/lib/querys/serverStats.ts b/frontend/src/lib/querys/serverStats.ts index ca4695b..6013ecf 100644 --- a/frontend/src/lib/querys/serverStats.ts +++ b/frontend/src/lib/querys/serverStats.ts @@ -5,13 +5,13 @@ export function getServerStats() { return queryOptions({ queryKey: ["getServerStats"], queryFn: () => fetchSession(), - staleTime: 5000, + staleTime: 2 * 60 * 1000, refetchOnWindowFocus: true, }); } const fetchSession = async () => { - const { data } = await axios.get(`/lst/api/system/stats`); + const { data } = await axios.get(`/lst/api/system/allservers/stats`); return data; }; diff --git a/frontend/src/routes/_app/_adminLayout/admin/servers.tsx b/frontend/src/routes/_app/_adminLayout/admin/servers.tsx index a324cd0..521ac50 100644 --- a/frontend/src/routes/_app/_adminLayout/admin/servers.tsx +++ b/frontend/src/routes/_app/_adminLayout/admin/servers.tsx @@ -15,6 +15,7 @@ import React, { useEffect, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { Skeleton } from "@/components/ui/skeleton"; import { Table, TableBody, @@ -24,6 +25,7 @@ import { TableRow, } from "@/components/ui/table"; import { getServers } from "@/lib/querys/admin/getServers"; +import { getServerStats } from "@/lib/querys/serverStats"; type ServerData = { name: string; @@ -34,6 +36,7 @@ type ServerData = { build: string; pendingUpdateFile: string; lastUpdate: Date; + memoryUsage: string; }; const updateServerItem = async ( @@ -58,14 +61,18 @@ export const Route = createFileRoute("/_app/_adminLayout/admin/servers")({ function RouteComponent() { const { data, isLoading, refetch } = useQuery(getServers()); - // const { - // data: stats, - // isLoading: loadingStats, - // refetch: statsRefetch, - // } = useQuery(getServerStats()); + const { data: stats = [], isLoading: loadingStats } = useQuery( + getServerStats(), + ); const [sorting, setSorting] = useState([]); const columnHelper = createColumnHelper(); + const statsMap = React.useMemo(() => { + const map = new Map(); + stats.forEach((stat: any) => map.set(stat.plantToken, stat)); + return map; + }, [stats]); + const updateServer = useMutation({ mutationFn: ({ token, @@ -197,15 +204,81 @@ function RouteComponent() { }, }), - // columnHelper.accessor("uptime", { - // header: () => { - // return UpTime; - // }, - // cell: ({ row }) => { - // //console.log(data); - // return {data?.uptime ?? "--"}; - // }, - // }), + columnHelper.accessor("uptime", { + header: () => { + return UpTime; + }, + cell: ({ row }) => { + const statsRow = statsMap.get(row.original.plantToken); + + if (loadingStats) { + // use Skeleton from shadcn/ui (or any placeholder) + return ; + } + console.log(statsMap); + return {statsRow ? `${statsRow.uptime.toFixed(2)}` : "--"}; + }, + }), + columnHelper.accessor("lastUpdate", { + header: () => { + return Last Updated; + }, + cell: ({ row }) => { + const statsRow = statsMap.get(row.original.plantToken); + + if (loadingStats) { + // use Skeleton from shadcn/ui (or any placeholder) + return ; + } + console.log(statsMap); + return {statsRow ? `${statsRow.lastUpdate}` : "--"}; + }, + }), + columnHelper.accessor("build", { + header: () => { + return Build Number; + }, + cell: ({ row }) => { + const statsRow = statsMap.get(row.original.plantToken); + + if (loadingStats) { + // use Skeleton from shadcn/ui (or any placeholder) + return ; + } + console.log(statsMap); + return {statsRow ? `${statsRow.build}` : "--"}; + }, + }), + columnHelper.accessor("pendingUpdateFile", { + header: () => { + return Pending Update; + }, + cell: ({ row }) => { + const statsRow = statsMap.get(row.original.plantToken); + + if (loadingStats) { + // use Skeleton from shadcn/ui (or any placeholder) + return ; + } + console.log(statsMap); + return {statsRow ? `${statsRow.pendingUpdateFile}` : "--"}; + }, + }), + columnHelper.accessor("memoryUsage", { + header: () => { + return Memory Usage; + }, + cell: ({ row }) => { + const statsRow = statsMap.get(row.original.plantToken); + + if (loadingStats) { + // use Skeleton from shadcn/ui (or any placeholder) + return ; + } + //console.log(statsMap); + return {statsRow ? `${statsRow.memoryUsage}` : "--"}; + }, + }), ]; const table = useReactTable({