From ccf7b8d4aa5895b1430f4554924923bd122cffc4 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Sun, 21 Apr 2024 17:54:12 +0800 Subject: [PATCH] refactor: redesign servers table in new design --- src/client/components/DataTable.tsx | 80 ++++++++++ src/client/components/server/ServerList.tsx | 159 +++++++++++++++++++ src/client/components/server/useServerMap.ts | 11 ++ src/client/components/ui/table.tsx | 8 +- src/client/pages/Servers.tsx | 3 + src/client/routes/server.tsx | 11 +- 6 files changed, 261 insertions(+), 11 deletions(-) create mode 100644 src/client/components/DataTable.tsx create mode 100644 src/client/components/server/ServerList.tsx create mode 100644 src/client/components/server/useServerMap.ts diff --git a/src/client/components/DataTable.tsx b/src/client/components/DataTable.tsx new file mode 100644 index 0000000..f99c897 --- /dev/null +++ b/src/client/components/DataTable.tsx @@ -0,0 +1,80 @@ +import { + ColumnDef, + flexRender, + getCoreRowModel, + useReactTable, + createColumnHelper, +} from '@tanstack/react-table'; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { Empty } from 'antd'; + +export type { ColumnDef }; +export { createColumnHelper }; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ columns, data }: DataTableProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + + + + )} + +
+
+ ); +} diff --git a/src/client/components/server/ServerList.tsx b/src/client/components/server/ServerList.tsx new file mode 100644 index 0000000..c6eba68 --- /dev/null +++ b/src/client/components/server/ServerList.tsx @@ -0,0 +1,159 @@ +import React, { useMemo } from 'react'; +import { DataTable, createColumnHelper } from '../DataTable'; +import { useTranslation } from '@i18next-toolkit/react'; +import { useIntervalUpdate } from '@/hooks/useIntervalUpdate'; +import { useServerMap } from './useServerMap'; +import { isServerOnline } from '@tianji/shared'; +import { max } from 'lodash-es'; +import { ServerStatusInfo } from '../../../types'; +import { Badge } from 'antd'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; +import dayjs from 'dayjs'; +import { filesize } from 'filesize'; +import prettyMilliseconds from 'pretty-ms'; +import { UpDownCounter } from '../UpDownCounter'; +import { ScrollArea, ScrollBar } from '../ui/scroll-area'; + +const columnHelper = createColumnHelper(); + +interface ServerListProps { + hideOfflineServer: boolean; +} +export const ServerList: React.FC = React.memo((props) => { + const { t } = useTranslation(); + const serverMap = useServerMap(); + const inc = useIntervalUpdate(2 * 1000); + const { hideOfflineServer } = props; + + const dataSource = useMemo( + () => + Object.values(serverMap) + .sort((info) => (isServerOnline(info) ? -1 : 1)) + .filter((info) => { + if (hideOfflineServer) { + return isServerOnline(info); + } + + return true; + }), // make online server is up and offline is down + [serverMap, inc, hideOfflineServer] + ); + const lastUpdatedAt = max(dataSource.map((d) => d.updatedAt)); + + const columns = useMemo(() => { + return [ + columnHelper.display({ + header: t('Status'), + size: 90, + cell: (props) => + isServerOnline(props.row.original) ? ( + + ) : ( + + + + + + {t('Last online: {{time}}', { + time: dayjs(props.row.original.updatedAt).format( + 'YYYY-MM-DD HH:mm:ss' + ), + })} + + + ), + }), + columnHelper.accessor('name', { + header: t('Node Name'), + size: 150, + }), + columnHelper.accessor('hostname', { + header: t('Host Name'), + size: 150, + }), + columnHelper.accessor('payload.uptime', { + header: t('Uptime'), + size: 150, + cell: (props) => prettyMilliseconds(Number(props.getValue()) * 1000), + }), + columnHelper.accessor('payload.load', { + header: t('Load'), + size: 70, + }), + columnHelper.display({ + header: t('Network'), + size: 110, + cell: (props) => ( + + ), + }), + columnHelper.display({ + header: t('Traffic'), + size: 130, + cell: (props) => ( + + ), + }), + columnHelper.accessor('payload.cpu', { + header: 'CPU', + size: 80, + cell: (props) => `${props.getValue()}%`, + }), + columnHelper.display({ + header: 'RAM', + size: 120, + cell: (props) => ( +
+
+ {filesize(props.row.original.payload.memory_used * 1000)} /{' '} +
+
+ {filesize(props.row.original.payload.memory_total * 1000)} +
+
+ ), + }), + columnHelper.display({ + header: 'HDD', + size: 120, + cell: (props) => ( +
+
+ {filesize(props.row.original.payload.hdd_used * 1000 * 1000)} /{' '} +
+
+ {filesize(props.row.original.payload.hdd_total * 1000 * 1000)} +
+
+ ), + }), + columnHelper.accessor('updatedAt', { + header: t('updatedAt'), + size: 130, + cell: (props) => dayjs(props.getValue()).format('MMM D HH:mm:ss'), + }), + ]; + }, []); + + return ( +
+
+ {t('Last updated at: {{date}}', { + date: dayjs(lastUpdatedAt).format('YYYY-MM-DD HH:mm:ss'), + })} +
+ + + + + +
+ ); +}); +ServerList.displayName = 'ServerList'; diff --git a/src/client/components/server/useServerMap.ts b/src/client/components/server/useServerMap.ts new file mode 100644 index 0000000..863345a --- /dev/null +++ b/src/client/components/server/useServerMap.ts @@ -0,0 +1,11 @@ +import { useSocketSubscribe } from '@/api/socketio'; +import { ServerStatusInfo } from '../../../types'; + +export function useServerMap(): Record { + const serverMap = useSocketSubscribe>( + 'onServerStatusUpdate', + {} + ); + + return serverMap; +} diff --git a/src/client/components/ui/table.tsx b/src/client/components/ui/table.tsx index 5dabff1..93ef1e3 100644 --- a/src/client/components/ui/table.tsx +++ b/src/client/components/ui/table.tsx @@ -43,7 +43,7 @@ const TableFooter = React.forwardRef< tr]:last:border-b-0 dark:bg-zinc-800/50', + 'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className )} {...props} @@ -58,7 +58,7 @@ const TableRow = React.forwardRef< [role=checkbox]]:translate-y-[2px] dark:text-zinc-400', + 'text-muted-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className )} {...props} @@ -102,7 +102,7 @@ const TableCaption = React.forwardRef< >(({ className, ...props }, ref) => ( )); diff --git a/src/client/pages/Servers.tsx b/src/client/pages/Servers.tsx index 03181cb..d817e79 100644 --- a/src/client/pages/Servers.tsx +++ b/src/client/pages/Servers.tsx @@ -134,6 +134,9 @@ function useServerMap(): Record { return serverMap; } +/** + * @deprecated + */ export const ServerList: React.FC<{ hideOfflineServer: boolean; }> = React.memo((props) => { diff --git a/src/client/routes/server.tsx b/src/client/routes/server.tsx index 2568a23..3d49b76 100644 --- a/src/client/routes/server.tsx +++ b/src/client/routes/server.tsx @@ -1,15 +1,12 @@ import { defaultErrorHandler, trpc } from '@/api/trpc'; import { CommonHeader } from '@/components/CommonHeader'; -import { CommonList } from '@/components/CommonList'; import { CommonWrapper } from '@/components/CommonWrapper'; +import { ServerList } from '@/components/server/ServerList'; import { AlertDialog, AlertDialogAction, AlertDialogContent, - AlertDialogDescription, AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog'; import { Button } from '@/components/ui/button'; @@ -19,7 +16,7 @@ import { Switch } from '@/components/ui/switch'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { useEventWithLoading } from '@/hooks/useEvent'; import { LayoutV2 } from '@/pages/LayoutV2'; -import { AddServerStep, InstallScript, ServerList } from '@/pages/Servers'; +import { AddServerStep, InstallScript } from '@/pages/Servers'; import { useCurrentWorkspaceId } from '@/store/user'; import { routeAuthBeforeLoad } from '@/utils/route'; import { useTranslation } from '@i18next-toolkit/react'; @@ -115,9 +112,9 @@ export const ServerContent: React.FC = React.memo(() => { /> } > - +
- +
); });