import { useMutation, useQuery } from "@tanstack/react-query"; import { createFileRoute } from "@tanstack/react-router"; import { createColumnHelper, flexRender, getCoreRowModel, getPaginationRowModel, getSortedRowModel, type SortingState, useReactTable, } from "@tanstack/react-table"; import axios from "axios"; import { ArrowDown, ArrowUp } from "lucide-react"; 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, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { getServers } from "@/lib/querys/admin/getServers"; import { getServerStats } from "@/lib/querys/serverStats"; type ServerData = { name: string; serverDNS: string; plantToken: string; ipAddress: string; uptime: string; build: string; pendingUpdateFile: string; lastUpdate: Date; memoryUsage: string; greatPlainsPlantCode: string; }; const updateServerItem = async ( token: string, data: Record, ) => { try { const res = axios.patch(`/lst/api/admin/server/${token}`, data, { withCredentials: true, }); toast.success(`Updated: ${data.field} set to ${data.value}`); return res; } catch (err) { toast.error(`Error updating: ${data.field} being set to ${data.value}`); return err; } }; export const Route = createFileRoute("/_app/_adminLayout/admin/servers")({ component: RouteComponent, }); function RouteComponent() { const { data, isLoading, refetch } = useQuery(getServers()); const { data: stats = [], isLoading: loadingStats } = useQuery( getServerStats(), ); const [sorting, setSorting] = useState([]); const columnHelper = createColumnHelper(); const [pagination, setPagination] = useState({ pageIndex: 0, //initial page index pageSize: 20, //default page size }); 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, field, value, }: { token: string; field: string; value: string | number | boolean | null; }) => updateServerItem(token, { [field]: value }), onSuccess: () => { // refetch or update cache refetch(); }, }); const columns = [ columnHelper.accessor("name", { cell: ({ row, getValue }) => { let url = ""; if (row.original.plantToken.includes("test")) { url = `https://${row.original.serverDNS}.alpla.net/lst/app`; } else { url = `https://${row.original.plantToken}prod.alpla.net/lst/app`; } return ( {getValue()} ); }, header: ({ column }) => { return ( ); }, }), columnHelper.accessor("serverDNS", { header: ({ column }) => { return ( ); }, cell: ({ row, getValue }) => { const initialValue = String(getValue() ?? ""); const [localValue, setLocalValue] = React.useState(initialValue); const token = row.original.plantToken; const field = "serverDNS"; useEffect(() => setLocalValue(initialValue), [initialValue]); const handleSubmit = (newValue: string) => { if (newValue !== initialValue) { setLocalValue(newValue); // keep new value locally immediately updateServer.mutate({ token, field, value: newValue }); } }; let submitting = false; return ( setLocalValue(e.currentTarget.value)} onBlur={(e) => { if (!submitting) { submitting = true; handleSubmit(e.currentTarget.value.trim()); setTimeout(() => (submitting = false), 100); // reset after slight delay } }} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); handleSubmit(e.currentTarget.value.trim()); e.currentTarget.blur(); // exit edit mode setTimeout(() => (submitting = false), 100); } }} /> ); }, }), columnHelper.accessor("ipAddress", { header: ({ column }) => { return ( ); }, cell: ({ row, getValue }) => { const initialValue = String(getValue() ?? ""); const [localValue, setLocalValue] = React.useState(initialValue); const token = row.original.plantToken; const field = "ipAddress"; useEffect(() => setLocalValue(initialValue), [initialValue]); const handleSubmit = (newValue: string) => { if (newValue !== initialValue) { setLocalValue(newValue); // keep new value locally immediately updateServer.mutate({ token, field, value: newValue }); } }; return ( setLocalValue(e.currentTarget.value)} onBlur={(e) => handleSubmit(e.currentTarget.value.trim())} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); handleSubmit(e.currentTarget.value.trim()); e.currentTarget.blur(); // exit edit mode } }} /> ); }, }), columnHelper.accessor("greatPlainsPlantCode", { header: () => { return GP Code; }, cell: ({ row, getValue }) => { const initialValue = String(getValue() ?? ""); const [localValue, setLocalValue] = React.useState(initialValue); const token = row.original.plantToken; const field = "greatPlainsPlantCode"; useEffect(() => setLocalValue(initialValue), [initialValue]); const handleSubmit = (newValue: string) => { if (newValue !== initialValue) { setLocalValue(newValue); // keep new value locally immediately updateServer.mutate({ token, field, value: newValue }); } }; return ( setLocalValue(e.currentTarget.value)} onBlur={(e) => handleSubmit(e.currentTarget.value.trim())} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); handleSubmit(e.currentTarget.value.trim()); e.currentTarget.blur(); // exit edit mode } }} /> ); }, }), 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)}` : "Server Offline?"} ); }, }), 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}` : "Server Offline?"} ); }, }), 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}` : "Server Offline?"} ); }, }), 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 === null ? "nothing pending" : statsRow.pendingUpdateFile}` : "Server Offline?"} ); }, }), 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}` : "Server Offline?"} ); }, }), ]; const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), onPaginationChange: setPagination, //renderSubComponent: ({ row }: { row: any }) => , //getRowCanExpand: () => true, state: { sorting, pagination, }, }); if (isLoading) { return
Loading user data
; } return (
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext(), )} ); })} ))} {table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ))} {/* {row.getIsExpanded() && ( {renderSubComponent({ row })} )} */} ))}
); }