diff --git a/beszel/site/src/components/systems-table/systems-table.tsx b/beszel/site/src/components/systems-table/systems-table.tsx index 66d306d..d5a994e 100644 --- a/beszel/site/src/components/systems-table/systems-table.tsx +++ b/beszel/site/src/components/systems-table/systems-table.tsx @@ -21,6 +21,9 @@ import { DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" @@ -50,41 +53,41 @@ import { HardDriveIcon, ServerIcon, CpuIcon, - ChevronDownIcon, LayoutGridIcon, LayoutListIcon, - XCircle, ArrowDownIcon, ArrowUpIcon, + Settings2Icon, + EyeIcon, } 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, useLocalStorage } from "@/lib/utils" import AlertsButton from "../alerts/alert-button" -import { navigate } from "../router" +import { Link, 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" +import { ClassValue } from "clsx" -type ViewMode = 'table' | 'grid' +type ViewMode = "table" | "grid" function CellFormatter(info: CellContext) { const val = info.getValue() as number return ( -
+
{decimalString(val, 1)}% @@ -92,16 +95,17 @@ function CellFormatter(info: CellContext) { ) } -function sortableHeader(column: Column, Icon: any, hideSortIcon = false) { +function sortableHeader(column: Column, hideSortIcon = false) { return ( ) } @@ -110,10 +114,10 @@ export default function SystemsTable() { const data = useStore($systems) const hubVersion = useStore($hubVersion) const [filter, setFilter] = useState() - const [sorting, setSorting] = useState([]) + const [sorting, setSorting] = useState([{ id: t`System`, desc: false }]) const [columnFilters, setColumnFilters] = useState([]) const [columnVisibility, setColumnVisibility] = useLocalStorage("cols", {}) - const [viewMode, setViewMode] = useLocalStorage('viewMode', 'table') + const [viewMode, setViewMode] = useLocalStorage("viewMode", window.innerWidth > 1024 ? "table" : "grid") const { i18n } = useLingui() useEffect(() => { @@ -131,66 +135,64 @@ export default function SystemsTable() { accessorKey: "name", id: t`System`, enableHiding: false, - cell: (info) => { - const { status } = info.row.original - return ( - - - - - ) - }, - header: ({ column }) => sortableHeader(column, ServerIcon), + icon: ServerIcon, + cell: (info) => ( + + + + + ), + header: ({ column }) => sortableHeader(column), }, { accessorKey: "info.cpu", id: t`CPU`, invertSorting: true, cell: CellFormatter, - header: ({ column }) => sortableHeader(column, CpuIcon), + icon: CpuIcon, + header: ({ column }) => sortableHeader(column), }, { accessorKey: "info.mp", id: t`Memory`, invertSorting: true, cell: CellFormatter, - header: ({ column }) => sortableHeader(column, MemoryStickIcon), + icon: MemoryStickIcon, + header: ({ column }) => sortableHeader(column), }, { accessorKey: "info.dp", id: t`Disk`, invertSorting: true, cell: CellFormatter, - header: ({ column }) => sortableHeader(column, HardDriveIcon), + icon: HardDriveIcon, + header: ({ column }) => sortableHeader(column), }, { accessorFn: (originalRow) => originalRow.info.b || 0, id: t`Net`, invertSorting: true, size: 115, - header: ({ column }) => sortableHeader(column, EthernetIcon), - cell: (info) => { + icon: EthernetIcon, + header: ({ column }) => sortableHeader(column), + cell(info) { const val = info.getValue() as number return ( - {decimalString(val, val >= 100 ? 1 : 2)} MB/s + + {decimalString(val, val >= 100 ? 1 : 2)} MB/s + ) }, }, @@ -199,20 +201,23 @@ export default function SystemsTable() { id: t`Agent`, invertSorting: true, size: 50, - header: ({ column }) => sortableHeader(column, WifiIcon, true), - cell: (info) => { + icon: WifiIcon, + header: ({ column }) => sortableHeader(column, true), + cell(info) { const version = info.getValue() as string if (!version || !hubVersion) { return null } return ( - - + + {info.getValue() as string} ) @@ -221,83 +226,12 @@ export default function SystemsTable() { { id: t({ message: "Actions", comment: "Table column" }), size: 120, - cell: ({ row }) => { - const { id, name, status, host } = row.original - return ( -
- - - - - - - - { - pb.collection("systems").update(id, { - status: status === "paused" ? "pending" : "paused", - }) - }} - > - {status === "paused" ? ( - <> - - Resume - - ) : ( - <> - - Pause - - )} - - copyToClipboard(host)}> - - Copy host - - - - - - Delete - - - - - - - - Are you sure you want to delete {name}? - - - - This action cannot be undone. This will permanently delete all current records for {name} from - the database. - - - - - - Cancel - - pb.collection("systems").delete(id)} - > - Continue - - - - -
- ) - }, + cell: ({ row }) => ( +
+ + +
+ ), }, ] as ColumnDef[] }, [hubVersion, i18n.locale]) @@ -335,88 +269,96 @@ export default function SystemsTable() { Updated in real time. Click on a system to view information.
-
+
setFilter(e.target.value)} className="px-4" /> - -
-
- - View Type - - setViewMode('table')} - className="gap-2" + +
+
+ + + Layout + + + setViewMode(view as ViewMode)} > - - Table - - setViewMode('grid')} - className="gap-2" - > - - Grid - + e.preventDefault()} className="gap-2"> + + Table + + e.preventDefault()} className="gap-2"> + + Grid + +
-
- - Sort by - - {table.getAllColumns().map((column) => { - if (column.id === t`Actions` || !column.getCanSort()) return null - - const isCurrentSort = sorting[0]?.id === column.id - const sortDirection = sorting[0]?.desc ? : - - return ( - { - const isDesc = sorting[0]?.id === column.id && !sorting[0]?.desc - setSorting([{ id: column.id, desc: isDesc }]) - }} - > - {isCurrentSort && sortDirection} - {column.id} - - ) - })} - {sorting.length > 0 && ( - setSorting([])}> - - Clear Sort - - )} -
- -
- - Visible Fields - - {table - .getAllColumns() - .filter((column) => column.getCanHide()) - .map((column) => { +
+ + + Sort By + + +
+ {table.getAllColumns().map((column) => { + if (column.id === t`Actions` || !column.getCanSort()) return null + let Icon = + // if current sort column, show sort direction + if (sorting[0]?.id === column.id) { + if (sorting[0]?.desc) { + Icon = + } else { + Icon = + } + } return ( - { + e.preventDefault() + setSorting([{ id: column.id, desc: sorting[0]?.id === column.id && !sorting[0]?.desc }]) + }} key={column.id} - checked={column.getIsVisible()} - onCheckedChange={(value) => column.toggleVisibility(!!value)} > + {Icon} {column.id} - + ) })} +
+
+ +
+ + + Visible Fields + + +
+ {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + e.preventDefault()} + checked={column.getIsVisible()} + onCheckedChange={(value) => column.toggleVisibility(!!value)} + > + {column.id} + + ) + })} +
@@ -424,8 +366,9 @@ export default function SystemsTable() {
-
- {viewMode === 'table' ? ( +
+ {viewMode === "table" ? ( + // table layout
@@ -434,7 +377,9 @@ export default function SystemsTable() { {headerGroup.headers.map((header) => { return ( - {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} ) })} @@ -481,145 +426,69 @@ export default function SystemsTable() {
) : ( + // grid layout
{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.original.name} + table.getRowModel().rows.map((row) => { + const system = row.original + const { status } = system + return ( + + +
+ +
+ + + {system.name} + +
+ {table.getColumn(t`Actions`)?.getIsVisible() && ( +
+ + +
+ )}
- {table.getColumn(t`Actions`)?.getIsVisible() && ( -
- - - - - - - - { - pb.collection("systems").update(row.original.id, { - status: row.original.status === "paused" ? "pending" : "paused", - }) - }} - > - {row.original.status === "paused" ? ( - <> - - Resume - - ) : ( - <> - - Pause - - )} - - copyToClipboard(row.original.host)}> - - Copy host - - - - - - Delete - - - - - - - - Are you sure you want to delete {row.original.name}? - - - - This action cannot be undone. This will permanently delete all current records for {row.original.name} from - the database. - - - - - - Cancel - - pb.collection("systems").delete(row.original.id)} - > - Continue - - - - -
- )} -
- - - {table.getAllColumns().map((column) => { - if (!column.getIsVisible() || column.id === t`System` || column.id === t`Actions`) return null - const cell = row.getAllCells().find(cell => cell.column.id === column.id) - if (!cell) return null - - const icon = (() => { - switch (column.id) { - case t`CPU`: return - case t`Memory`: return - case t`Disk`: return - case t`Net`: return - case t`Agent`: return - default: return null - } - })() - - return ( -
- {icon} -
- {column.id}: -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} + + + {table.getAllColumns().map((column) => { + if (!column.getIsVisible() || column.id === t`System` || column.id === t`Actions`) return null + const cell = row.getAllCells().find((cell) => cell.column.id === column.id) + if (!cell) return null + return ( +
+ {/* @ts-ignore */} + {column.columnDef?.icon && ( + // @ts-ignore + + )} +
+ {column.id}: +
{flexRender(cell.column.columnDef.cell, cell.getContext())}
-
- ) - })} - - - )) + ) + })} + + + {row.original.name} + + + ) + }) ) : (
No systems found. @@ -631,3 +500,94 @@ export default function SystemsTable() { ) } + +function IndicatorDot({ system, className }: { system: SystemRecord; className?: ClassValue }) { + className ||= { + "bg-green-500": system.status === "up", + "bg-red-500": system.status === "down", + "bg-primary/40": system.status === "paused", + "bg-yellow-500": system.status === "pending", + } + return ( + + ) +} + +function ActionsButton({ system }: { system: SystemRecord }) { + // const [opened, setOpened] = useState(false) + const { id, status, host, name } = system + return ( + + + + + + + { + pb.collection("systems").update(id, { + status: status === "paused" ? "pending" : "paused", + }) + }} + > + {status === "paused" ? ( + <> + + Resume + + ) : ( + <> + + Pause + + )} + + copyToClipboard(host)}> + + Copy host + + + + + + Delete + + + + + + + + Are you sure you want to delete {name}? + + + + This action cannot be undone. This will permanently delete all current records for {name} from the + database. + + + + + + Cancel + + pb.collection("systems").delete(id)} + > + Continue + + + + + ) +}