diff --git a/beszel/site/bun.lockb b/beszel/site/bun.lockb index 773db0a..98e3b25 100755 Binary files a/beszel/site/bun.lockb and b/beszel/site/bun.lockb differ diff --git a/beszel/site/package.json b/beszel/site/package.json index 5f8c81a..3dece67 100644 --- a/beszel/site/package.json +++ b/beszel/site/package.json @@ -33,6 +33,7 @@ "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-table": "^8.21.3", + "@tanstack/react-virtual": "^3.13.12", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/beszel/site/src/components/add-system.tsx b/beszel/site/src/components/add-system.tsx index 4fba273..f3fd059 100644 --- a/beszel/site/src/components/add-system.tsx +++ b/beszel/site/src/components/add-system.tsx @@ -133,7 +133,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) => > - + {system ? `${t`Edit`} ${system?.name}` : Add New System} diff --git a/beszel/site/src/components/alerts-history-columns.tsx b/beszel/site/src/components/alerts-history-columns.tsx index 4bddce7..390bd5c 100644 --- a/beszel/site/src/components/alerts-history-columns.tsx +++ b/beszel/site/src/components/alerts-history-columns.tsx @@ -16,7 +16,9 @@ export const alertsHistoryColumns: ColumnDef[] = [ System ), - cell: ({ row }) => {row.original.expand?.system?.name || row.original.system}, + cell: ({ row }) => ( +
{row.original.expand?.system?.name || row.original.system}
+ ), filterFn: (row, _, filterValue) => { const display = row.original.expand?.system?.name || row.original.system || "" return display.toLowerCase().includes(filterValue.toLowerCase()) diff --git a/beszel/site/src/components/alerts/alerts-sheet.tsx b/beszel/site/src/components/alerts/alerts-sheet.tsx index 24539a7..561985c 100644 --- a/beszel/site/src/components/alerts/alerts-sheet.tsx +++ b/beszel/site/src/components/alerts/alerts-sheet.tsx @@ -96,7 +96,7 @@ export const AlertDialogContent = memo(function AlertDialogContent({ system }: { - {system.name} + {system.name} diff --git a/beszel/site/src/components/routes/home.tsx b/beszel/site/src/components/routes/home.tsx index 12e5ec2..7a540b2 100644 --- a/beszel/site/src/components/routes/home.tsx +++ b/beszel/site/src/components/routes/home.tsx @@ -44,7 +44,7 @@ export default memo(function () { -
+
{table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => ( {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} ))} - + ))} diff --git a/beszel/site/src/components/routes/settings/layout.tsx b/beszel/site/src/components/routes/settings/layout.tsx index e7e757e..d64ce6b 100644 --- a/beszel/site/src/components/routes/settings/layout.tsx +++ b/beszel/site/src/components/routes/settings/layout.tsx @@ -102,7 +102,7 @@ export default function SettingsLayout() { }, []) return ( - + Settings diff --git a/beszel/site/src/components/routes/settings/tokens-fingerprints.tsx b/beszel/site/src/components/routes/settings/tokens-fingerprints.tsx index d5d88b9..d734ac9 100644 --- a/beszel/site/src/components/routes/settings/tokens-fingerprints.tsx +++ b/beszel/site/src/components/routes/settings/tokens-fingerprints.tsx @@ -272,7 +272,7 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec
- + {headerCols.map((col) => ( @@ -288,12 +288,14 @@ const SectionTable = memo(({ fingerprints = [] }: { fingerprints: FingerprintRec )} - + {fingerprints.map((fingerprint, i) => ( - {fingerprint.expand.system.name} + + {fingerprint.expand.system.name} + {fingerprint.token} {fingerprint.fingerprint} {!isReadOnly && ( diff --git a/beszel/site/src/components/routes/system.tsx b/beszel/site/src/components/routes/system.tsx index cef2f14..65100a5 100644 --- a/beszel/site/src/components/routes/system.tsx +++ b/beszel/site/src/components/routes/system.tsx @@ -391,7 +391,7 @@ export default function SystemDetail({ name }: { name: string }) { return ( <> -
+
{/* system info */}
diff --git a/beszel/site/src/components/systems-table/systems-table-columns.tsx b/beszel/site/src/components/systems-table/systems-table-columns.tsx index 27746a4..20864a7 100644 --- a/beszel/site/src/components/systems-table/systems-table-columns.tsx +++ b/beszel/site/src/components/systems-table/systems-table-columns.tsx @@ -27,7 +27,7 @@ import { } from "@/lib/utils" import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons" import { useStore } from "@nanostores/react" -import { $userSettings } from "@/lib/stores" +import { $longestSystemNameLen, $userSettings } from "@/lib/stores" import { Trans, useLingui } from "@lingui/react/macro" import { useMemo, useRef, useState } from "react" import { memo } from "react" @@ -111,11 +111,14 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD Icon: ServerIcon, cell: (info) => { const { name } = info.row.original + const longestName = useStore($longestSystemNameLen) return ( <> - {name} + + {name} + {viewMode === "table" ? ( // table layout -
+
) : ( @@ -277,36 +278,78 @@ export default function SystemsTable() { ) } -const AllSystemsTable = memo( - ({ table, rows, colLength }: { table: TableType; rows: Row[]; colLength: number }) => { - return ( -
- - - {rows.length ? ( - rows.map((row) => ( - - )) - ) : ( - - - No systems found. - - - )} - -
- ) - } -) +const AllSystemsTable = memo(function ({ + table, + rows, + colLength, +}: { + table: TableType + rows: Row[] + colLength: number +}) { + // The virtualizer will need a reference to the scrollable container element + const scrollRef = useRef(null) + + const virtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => (rows.length > 10 ? 56 : 60), + getScrollElement: () => scrollRef.current, + overscan: 5, + }) + const virtualRows = virtualizer.getVirtualItems() + + const paddingTop = Math.max(0, virtualRows[0]?.start ?? 0 - virtualizer.options.scrollMargin) + const paddingBottom = Math.max(0, virtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0)) + + return ( +
2) && "min-h-50" + )} + ref={scrollRef} + > + {/* add header height to table size */} +
+ + + + {rows.length ? ( + virtualRows.map((virtualRow) => { + const row = rows[virtualRow.index] as Row + return ( + + ) + }) + ) : ( + + + No systems found. + + + )} + +
+
+
+ ) +}) function SystemsTableHead({ table, colLength }: { table: TableType; colLength: number }) { const { i18n } = useLingui() + return useMemo(() => { return ( - + {table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => { return ( @@ -314,41 +357,49 @@ function SystemsTableHead({ table, colLength }: { table: TableType ) })} - + ))} ) }, [i18n.locale, colLength]) } -const SystemTableRow = memo( - ({ row, length, colLength }: { row: Row; length: number; colLength: number }) => { - const system = row.original - const { t } = useLingui() - return useMemo(() => { - return ( - - {row.getVisibleCells().map((cell) => ( - 10 ? "py-2" : "py-2.5"} - > - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ) - }, [system, system.status, colLength, t]) - } -) +const SystemTableRow = memo(function ({ + row, + virtualRow, + colLength, +}: { + row: Row + virtualRow: VirtualItem + length: number + colLength: number +}) { + const system = row.original + const { t } = useLingui() + return useMemo(() => { + return ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ) + }, [system, system.status, colLength, t]) +}) const SystemCard = memo( ({ row, table, colLength }: { row: Row; table: TableType; colLength: number }) => { @@ -368,13 +419,11 @@ const SystemCard = memo( )} > -
- -
+
+ +
- - {system.name} - + {system.name}
{table.getColumn("actions")?.getIsVisible() && ( diff --git a/beszel/site/src/components/ui/command.tsx b/beszel/site/src/components/ui/command.tsx index 4b5c22f..31f8f92 100644 --- a/beszel/site/src/components/ui/command.tsx +++ b/beszel/site/src/components/ui/command.tsx @@ -101,7 +101,7 @@ function CommandItem({ className, ...props }: React.ComponentProps>( ({ className, ...props }, ref) => ( - + ) ) TableHeader.displayName = "TableHeader" diff --git a/beszel/site/src/index.css b/beszel/site/src/index.css index 5b62e44..fa63fd4 100644 --- a/beszel/site/src/index.css +++ b/beszel/site/src/index.css @@ -1,14 +1,15 @@ @import "tailwindcss"; @import "tw-animate-css"; +@custom-variant light (&:is(.light *)); @custom-variant dark (&:is(.dark *)); @custom-variant safari (@supports (hanging-punctuation: first) and (-webkit-appearance: none)); :root { --background: hsl(30 8% 98%); - --foreground: hsl(30 0% 0%); + --foreground: hsl(30 0% 10%); --card: hsl(30 0% 100%); - --card-foreground: hsl(240 6.67% 2.94%); + --card-foreground: hsl(240 6% 12%); --popover: hsl(30 0% 100%); --popover-foreground: hsl(240 10% 6.2%); --primary: hsl(240 5.88% 10%); @@ -30,6 +31,7 @@ --chart-3: hsl(30 80% 55%); --chart-4: hsl(280 65% 60%); --chart-5: hsl(340 75% 55%); + --table-header: hsl(225, 6%, 97%); } .dark { @@ -53,6 +55,7 @@ --border: hsl(220 3% 16%); --input: hsl(220 4% 22%); --ring: hsl(220 4% 80%); + --table-header: hsl(220, 6%, 13%); --radius: 0.8rem; } @@ -103,6 +106,7 @@ --color-chart-3: var(--chart-3); --color-chart-4: var(--chart-4); --color-chart-5: var(--chart-5); + --color-table-header: var(--table-header); } @layer utilities { diff --git a/beszel/site/src/lib/api.ts b/beszel/site/src/lib/api.ts index 1d16ee7..6ae6a44 100644 --- a/beszel/site/src/lib/api.ts +++ b/beszel/site/src/lib/api.ts @@ -1,5 +1,5 @@ import { ChartTimes, SystemRecord, UserSettings } from "@/types" -import { $alerts, $systems, $userSettings } from "./stores" +import { $alerts, $longestSystemNameLen, $systems, $userSettings } from "./stores" import { toast } from "@/components/ui/use-toast" import { t } from "@lingui/core/macro" import { chartTimeData } from "./utils" @@ -88,11 +88,29 @@ export const updateSystemList = (() => { } isFetchingSystems = true try { - const records = await pb + let records = await pb .collection("systems") .getFullList({ sort: "+name", fields: "id,name,host,port,info,status" }) if (records.length) { + // records = [ + // ...records, + // ...records, + // ...records, + // ...records, + // ...records, + // ...records, + // ...records, + // ...records, + // ...records, + // ] + // we need to loop once to get the longest name + let longestName = $longestSystemNameLen.get() + for (const { name } of records) { + if (name.length > longestName) { + $longestSystemNameLen.set(Math.min(20, name.length)) + } + } $systems.set(records) } else { verifyAuth() diff --git a/beszel/site/src/lib/stores.ts b/beszel/site/src/lib/stores.ts index 9a0ed48..f76b6db 100644 --- a/beszel/site/src/lib/stores.ts +++ b/beszel/site/src/lib/stores.ts @@ -53,3 +53,8 @@ export const $copyContent = atom("") /** Direction for localization */ export const $direction = atom<"ltr" | "rtl">("ltr") + +/** Longest system name length. Used to set table column width. I know this + * is stupid but the table is virtualized and I know this will work. + */ +export const $longestSystemNameLen = atom(8) diff --git a/beszel/site/src/main.tsx b/beszel/site/src/main.tsx index 56a0c10..eaf0ea7 100644 --- a/beszel/site/src/main.tsx +++ b/beszel/site/src/main.tsx @@ -102,7 +102,7 @@ const Layout = () => {
-
+
{copyContent && ( diff --git a/site/src/components/systems-table/systems-table.tsx b/site/src/components/systems-table/systems-table.tsx deleted file mode 100644 index 57c177e..0000000 --- a/site/src/components/systems-table/systems-table.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { memo, useMemo } from "react" -import { Row, TableType } from "@tanstack/react-table" -import { useLingui } from "@lingui/react" -import { cn } from "@/lib/utils" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Link } from "@/components/ui/link" -import { getPagePath } from "@/lib/page-path" -import { useRouter } from "next/router" -import { flexRender } from "@tanstack/react-table" -import { ColumnDef } from "@tanstack/table-core" -import { SystemRecord } from "@/lib/types" -import { IndicatorDot } from "@/components/indicator-dot" -import { AlertsButton } from "@/components/alerts-button" -import { ActionsButton } from "@/components/actions-button" -import { EyeIcon } from "@/components/icons" - -const SystemCard = memo( - ({ row, table, colLength }: { row: Row; table: TableType; colLength: number }) => { - const system = row.original - const { t } = useLingui() - - return useMemo(() => { - return ( - - -
- -
- - - {system.name} - -
-
- {table.getColumn("actions")?.getIsVisible() && ( -
- - -
- )} -
-
- - {table.getAllColumns().map((column) => { - if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null - const cell = row.getAllCells().find((cell) => cell.column.id === column.id) - if (!cell) return null - // @ts-ignore - const { Icon, name } = column.columnDef as ColumnDef - - // Special case for 'lastSeen' column: add EyeIcon before value - if (column.id === "lastSeen") { - return ( -
- -
- {name()}: -
{flexRender(cell.column.columnDef.cell, cell.getContext())}
-
-
- ) - } - - return ( -
- {Icon && } -
- {name()}: -
{flexRender(cell.column.columnDef.cell, cell.getContext())}
-
-
- ) - })} -
- - {row.original.name} - -
- ) - }, [system, colLength, t]) - } -) \ No newline at end of file diff --git a/supplemental/CHANGELOG.md b/supplemental/CHANGELOG.md index 304611d..02b9cfd 100644 --- a/supplemental/CHANGELOG.md +++ b/supplemental/CHANGELOG.md @@ -4,10 +4,16 @@ - Add status filters to All Systems table. +- Virtualize All Systems table to improve performance with hundreds of systems. (#1100) + - Fix Safari system link CSS bug. - Use older cuda image for increased compatibility (#1103) +- Truncate long system names in All Systems table. (#1104) + +- Fix update mirror and add `--china-mirrors` flag. (#1035) + ## 0.12.5 - Downgrade `gopsutil` to `v4.25.6` to fix panic on FreeBSD (#1083)