From 44747e75b026a37b59ea3df73fcaa57ad134fa9d Mon Sep 17 00:00:00 2001 From: Henry Dollman Date: Sat, 2 Nov 2024 13:35:14 -0400 Subject: [PATCH] add columns filter for systems table --- beszel/site/src/components/routes/home.tsx | 36 +--- .../systems-table/systems-table.tsx | 191 ++++++++++++------ 2 files changed, 131 insertions(+), 96 deletions(-) diff --git a/beszel/site/src/components/routes/home.tsx b/beszel/site/src/components/routes/home.tsx index ff8d351..419d9be 100644 --- a/beszel/site/src/components/routes/home.tsx +++ b/beszel/site/src/components/routes/home.tsx @@ -1,25 +1,22 @@ -import { Suspense, lazy, useEffect, useMemo, useState } from "react" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card" +import { Suspense, lazy, useEffect, useMemo } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "../ui/card" import { $alerts, $hubVersion, $systems, pb } from "@/lib/stores" import { useStore } from "@nanostores/react" import { GithubIcon } from "lucide-react" import { Separator } from "../ui/separator" import { alertInfo, updateRecordList, updateSystemList } from "@/lib/utils" import { AlertRecord, SystemRecord } from "@/types" -import { Input } from "../ui/input" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { Link } from "../router" import { Plural, t, Trans } from "@lingui/macro" -import { useLingui } from "@lingui/react" const SystemsTable = lazy(() => import("../systems-table/systems-table")) export default function Home() { const hubVersion = useStore($hubVersion) - const [filter, setFilter] = useState() + const alerts = useStore($alerts) const systems = useStore($systems) - const { _ } = useLingui() // todo: maybe remove active alert if changed const activeAlerts = useMemo(() => { @@ -99,30 +96,9 @@ export default function Home() { )} - - -
-
- - All Systems - - - Updated in real time. Click on a system to view information. - -
- setFilter(e.target.value)} - className="w-full md:w-56 lg:w-72 ms-auto px-4" - /> -
-
- - - - - -
+ + + {hubVersion && (
diff --git a/beszel/site/src/components/systems-table/systems-table.tsx b/beszel/site/src/components/systems-table/systems-table.tsx index 95e5a5c..671308b 100644 --- a/beszel/site/src/components/systems-table/systems-table.tsx +++ b/beszel/site/src/components/systems-table/systems-table.tsx @@ -6,6 +6,7 @@ import { SortingState, getSortedRowModel, flexRender, + VisibilityState, getCoreRowModel, useReactTable, Column, @@ -17,6 +18,7 @@ import { Button, buttonVariants } from "@/components/ui/button" import { DropdownMenu, + DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, @@ -48,15 +50,19 @@ import { HardDriveIcon, ServerIcon, CpuIcon, + ChevronDownIcon, } from "lucide-react" import { useEffect, useMemo, useState } from "react" import { $hubVersion, $systems, pb } from "@/lib/stores" import { useStore } from "@nanostores/react" -import { cn, copyToClipboard, decimalString, isReadOnlyUser } from "@/lib/utils" +import { cn, copyToClipboard, decimalString, isReadOnlyUser, useLocalStorage } from "@/lib/utils" import AlertsButton from "../alerts/alert-button" import { navigate } from "../router" import { EthernetIcon } from "../ui/icons" import { Trans, t } from "@lingui/macro" +import { useLingui } from "@lingui/react" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card" +import { Input } from "../ui/input" function CellFormatter(info: CellContext) { const val = info.getValue() as number @@ -76,7 +82,7 @@ function CellFormatter(info: CellContext) { ) } -function sortableHeader(column: Column, name: React.ReactNode, Icon: any, hideSortIcon = false) { +function sortableHeader(column: Column, Icon: any, hideSortIcon = false) { return ( ) } -export default function SystemsTable({ filter }: { filter?: string }) { +export default function SystemsTable() { const data = useStore($systems) const hubVersion = useStore($hubVersion) + const [filter, setFilter] = useState() const [sorting, setSorting] = useState([]) const [columnFilters, setColumnFilters] = useState([]) + const [columnVisibility, setColumnVisibility] = useLocalStorage("cols", {}) + const { i18n } = useLingui() useEffect(() => { if (filter !== undefined) { - table.getColumn("name")?.setFilterValue(filter) + table.getColumn(t`System`)?.setFilterValue(filter) } }, [filter]) @@ -109,6 +118,8 @@ export default function SystemsTable({ filter }: { filter?: string }) { size: 200, minSize: 0, accessorKey: "name", + id: t`System`, + enableHiding: false, cell: (info) => { const { status } = info.row.original return ( @@ -134,32 +145,35 @@ export default function SystemsTable({ filter }: { filter?: string }) { ) }, - header: ({ column }) => sortableHeader(column, t`System`, ServerIcon), + header: ({ column }) => sortableHeader(column, ServerIcon), }, { accessorKey: "info.cpu", + id: t`CPU`, invertSorting: true, cell: CellFormatter, - header: ({ column }) => sortableHeader(column, t`CPU`, CpuIcon), + header: ({ column }) => sortableHeader(column, CpuIcon), }, { accessorKey: "info.mp", + id: t`Memory`, invertSorting: true, cell: CellFormatter, - header: ({ column }) => sortableHeader(column, t`Memory`, MemoryStickIcon), + header: ({ column }) => sortableHeader(column, MemoryStickIcon), }, { accessorKey: "info.dp", + id: t`Disk`, invertSorting: true, cell: CellFormatter, - header: ({ column }) => sortableHeader(column, t`Disk`, HardDriveIcon), + header: ({ column }) => sortableHeader(column, HardDriveIcon), }, { accessorFn: (originalRow) => originalRow.info.b || 0, - id: "n", + id: t`Net`, invertSorting: true, size: 115, - header: ({ column }) => sortableHeader(column, t`Net`, EthernetIcon), + header: ({ column }) => sortableHeader(column, EthernetIcon), cell: (info) => { const val = info.getValue() as number return ( @@ -169,9 +183,10 @@ export default function SystemsTable({ filter }: { filter?: string }) { }, { accessorKey: "info.v", + id: t`Agent`, invertSorting: true, size: 50, - header: ({ column }) => sortableHeader(column, t`Agent`, WifiIcon, true), + header: ({ column }) => sortableHeader(column, WifiIcon, true), cell: (info) => { const version = info.getValue() as string if (!version || !hubVersion) { @@ -189,9 +204,8 @@ export default function SystemsTable({ filter }: { filter?: string }) { }, }, { - id: "actions", + id: t({ message: "Actions", comment: "Table column" }), size: 120, - // minSize: 0, cell: ({ row }) => { const { id, name, status, host } = row.original return ( @@ -271,7 +285,7 @@ export default function SystemsTable({ filter }: { filter?: string }) { }, }, ] as ColumnDef[] - }, [hubVersion]) + }, [hubVersion, i18n.locale]) const table = useReactTable({ data, @@ -281,9 +295,11 @@ export default function SystemsTable({ filter }: { filter?: string }) { getSortedRowModel: getSortedRowModel(), onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, state: { sorting, columnFilters, + columnVisibility, }, defaultColumn: { minSize: 0, @@ -293,59 +309,102 @@ export default function SystemsTable({ filter }: { filter?: string }) { }) 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) => ( - { - const target = e.target as HTMLElement - if (!target.closest("[data-nolink]") && e.currentTarget.contains(target)) { - navigate(`/system/${encodeURIComponent(row.original.name)}`) - } - }} - > - {row.getVisibleCells().map((cell) => ( - + +
+
+ + All Systems + + + Updated in real time. Click on a system to view information. + +
+
+ setFilter(e.target.value)} className="px-4" /> + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + column.toggleVisibility(!!value)} + > + {column.id} + + ) + })} + + +
+
+
+ +
+
+ + {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) => ( + { + const target = e.target as HTMLElement + if (!target.closest("[data-nolink]") && e.currentTarget.contains(target)) { + navigate(`/system/${encodeURIComponent(row.original.name)}`) + } }} - className={cn("overflow-hidden relative", data.length > 10 ? "py-2" : "py-2.5")} > - {flexRender(cell.column.columnDef.cell, cell.getContext())} + {row.getVisibleCells().map((cell) => ( + 10 ? "py-2" : "py-2.5")} + > + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No systems found. - ))} - - )) - ) : ( - - - No systems found. - - - )} - -
-
+ + )} + + +
+ + ) }