mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 17:59:28 +08:00
add columns filter for systems table
This commit is contained in:
@@ -1,25 +1,22 @@
|
|||||||
import { Suspense, lazy, useEffect, useMemo, useState } from "react"
|
import { Suspense, lazy, useEffect, useMemo } from "react"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
|
||||||
import { $alerts, $hubVersion, $systems, pb } from "@/lib/stores"
|
import { $alerts, $hubVersion, $systems, pb } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { GithubIcon } from "lucide-react"
|
import { GithubIcon } from "lucide-react"
|
||||||
import { Separator } from "../ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
import { alertInfo, updateRecordList, updateSystemList } from "@/lib/utils"
|
import { alertInfo, updateRecordList, updateSystemList } from "@/lib/utils"
|
||||||
import { AlertRecord, SystemRecord } from "@/types"
|
import { AlertRecord, SystemRecord } from "@/types"
|
||||||
import { Input } from "../ui/input"
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||||
import { Link } from "../router"
|
import { Link } from "../router"
|
||||||
import { Plural, t, Trans } from "@lingui/macro"
|
import { Plural, t, Trans } from "@lingui/macro"
|
||||||
import { useLingui } from "@lingui/react"
|
|
||||||
|
|
||||||
const SystemsTable = lazy(() => import("../systems-table/systems-table"))
|
const SystemsTable = lazy(() => import("../systems-table/systems-table"))
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const hubVersion = useStore($hubVersion)
|
const hubVersion = useStore($hubVersion)
|
||||||
const [filter, setFilter] = useState<string>()
|
|
||||||
const alerts = useStore($alerts)
|
const alerts = useStore($alerts)
|
||||||
const systems = useStore($systems)
|
const systems = useStore($systems)
|
||||||
const { _ } = useLingui()
|
|
||||||
|
|
||||||
// todo: maybe remove active alert if changed
|
// todo: maybe remove active alert if changed
|
||||||
const activeAlerts = useMemo(() => {
|
const activeAlerts = useMemo(() => {
|
||||||
@@ -99,30 +96,9 @@ export default function Home() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
<Card>
|
|
||||||
<CardHeader className="pb-5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
|
||||||
<div className="grid md:flex gap-5 w-full items-end">
|
|
||||||
<div className="px-2 sm:px-1">
|
|
||||||
<CardTitle className="mb-2.5">
|
|
||||||
<Trans>All Systems</Trans>
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
<Trans>Updated in real time. Click on a system to view information.</Trans>
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
<Input
|
|
||||||
placeholder={_(t`Filter...`)}
|
|
||||||
onChange={(e) => setFilter(e.target.value)}
|
|
||||||
className="w-full md:w-56 lg:w-72 ms-auto px-4"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="max-sm:p-2">
|
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<SystemsTable filter={filter} />
|
<SystemsTable />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{hubVersion && (
|
{hubVersion && (
|
||||||
<div className="flex gap-1.5 justify-end items-center pe-3 sm:pe-6 mt-3.5 text-xs opacity-80">
|
<div className="flex gap-1.5 justify-end items-center pe-3 sm:pe-6 mt-3.5 text-xs opacity-80">
|
||||||
|
@@ -6,6 +6,7 @@ import {
|
|||||||
SortingState,
|
SortingState,
|
||||||
getSortedRowModel,
|
getSortedRowModel,
|
||||||
flexRender,
|
flexRender,
|
||||||
|
VisibilityState,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
Column,
|
Column,
|
||||||
@@ -17,6 +18,7 @@ import { Button, buttonVariants } from "@/components/ui/button"
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
@@ -48,15 +50,19 @@ import {
|
|||||||
HardDriveIcon,
|
HardDriveIcon,
|
||||||
ServerIcon,
|
ServerIcon,
|
||||||
CpuIcon,
|
CpuIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
import { $hubVersion, $systems, pb } from "@/lib/stores"
|
import { $hubVersion, $systems, pb } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
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 AlertsButton from "../alerts/alert-button"
|
||||||
import { navigate } from "../router"
|
import { navigate } from "../router"
|
||||||
import { EthernetIcon } from "../ui/icons"
|
import { EthernetIcon } from "../ui/icons"
|
||||||
import { Trans, t } from "@lingui/macro"
|
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<SystemRecord, unknown>) {
|
function CellFormatter(info: CellContext<SystemRecord, unknown>) {
|
||||||
const val = info.getValue() as number
|
const val = info.getValue() as number
|
||||||
@@ -76,7 +82,7 @@ function CellFormatter(info: CellContext<SystemRecord, unknown>) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortableHeader(column: Column<SystemRecord, unknown>, name: React.ReactNode, Icon: any, hideSortIcon = false) {
|
function sortableHeader(column: Column<SystemRecord, unknown>, Icon: any, hideSortIcon = false) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -84,21 +90,24 @@ function sortableHeader(column: Column<SystemRecord, unknown>, name: React.React
|
|||||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
>
|
>
|
||||||
<Icon className="me-2 h-4 w-4" />
|
<Icon className="me-2 h-4 w-4" />
|
||||||
{name}
|
{column.id}
|
||||||
{!hideSortIcon && <ArrowUpDownIcon className="ms-2 h-4 w-4" />}
|
{!hideSortIcon && <ArrowUpDownIcon className="ms-2 h-4 w-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SystemsTable({ filter }: { filter?: string }) {
|
export default function SystemsTable() {
|
||||||
const data = useStore($systems)
|
const data = useStore($systems)
|
||||||
const hubVersion = useStore($hubVersion)
|
const hubVersion = useStore($hubVersion)
|
||||||
|
const [filter, setFilter] = useState<string>()
|
||||||
const [sorting, setSorting] = useState<SortingState>([])
|
const [sorting, setSorting] = useState<SortingState>([])
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||||
|
const [columnVisibility, setColumnVisibility] = useLocalStorage<VisibilityState>("cols", {})
|
||||||
|
const { i18n } = useLingui()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (filter !== undefined) {
|
if (filter !== undefined) {
|
||||||
table.getColumn("name")?.setFilterValue(filter)
|
table.getColumn(t`System`)?.setFilterValue(filter)
|
||||||
}
|
}
|
||||||
}, [filter])
|
}, [filter])
|
||||||
|
|
||||||
@@ -109,6 +118,8 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
size: 200,
|
size: 200,
|
||||||
minSize: 0,
|
minSize: 0,
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
|
id: t`System`,
|
||||||
|
enableHiding: false,
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const { status } = info.row.original
|
const { status } = info.row.original
|
||||||
return (
|
return (
|
||||||
@@ -134,32 +145,35 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
header: ({ column }) => sortableHeader(column, t`System`, ServerIcon),
|
header: ({ column }) => sortableHeader(column, ServerIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "info.cpu",
|
accessorKey: "info.cpu",
|
||||||
|
id: t`CPU`,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
cell: CellFormatter,
|
cell: CellFormatter,
|
||||||
header: ({ column }) => sortableHeader(column, t`CPU`, CpuIcon),
|
header: ({ column }) => sortableHeader(column, CpuIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "info.mp",
|
accessorKey: "info.mp",
|
||||||
|
id: t`Memory`,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
cell: CellFormatter,
|
cell: CellFormatter,
|
||||||
header: ({ column }) => sortableHeader(column, t`Memory`, MemoryStickIcon),
|
header: ({ column }) => sortableHeader(column, MemoryStickIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "info.dp",
|
accessorKey: "info.dp",
|
||||||
|
id: t`Disk`,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
cell: CellFormatter,
|
cell: CellFormatter,
|
||||||
header: ({ column }) => sortableHeader(column, t`Disk`, HardDriveIcon),
|
header: ({ column }) => sortableHeader(column, HardDriveIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (originalRow) => originalRow.info.b || 0,
|
accessorFn: (originalRow) => originalRow.info.b || 0,
|
||||||
id: "n",
|
id: t`Net`,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
size: 115,
|
size: 115,
|
||||||
header: ({ column }) => sortableHeader(column, t`Net`, EthernetIcon),
|
header: ({ column }) => sortableHeader(column, EthernetIcon),
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const val = info.getValue() as number
|
const val = info.getValue() as number
|
||||||
return (
|
return (
|
||||||
@@ -169,9 +183,10 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "info.v",
|
accessorKey: "info.v",
|
||||||
|
id: t`Agent`,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
size: 50,
|
size: 50,
|
||||||
header: ({ column }) => sortableHeader(column, t`Agent`, WifiIcon, true),
|
header: ({ column }) => sortableHeader(column, WifiIcon, true),
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const version = info.getValue() as string
|
const version = info.getValue() as string
|
||||||
if (!version || !hubVersion) {
|
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,
|
size: 120,
|
||||||
// minSize: 0,
|
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const { id, name, status, host } = row.original
|
const { id, name, status, host } = row.original
|
||||||
return (
|
return (
|
||||||
@@ -271,7 +285,7 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
] as ColumnDef<SystemRecord>[]
|
] as ColumnDef<SystemRecord>[]
|
||||||
}, [hubVersion])
|
}, [hubVersion, i18n.locale])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data,
|
data,
|
||||||
@@ -281,9 +295,11 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
onColumnFiltersChange: setColumnFilters,
|
onColumnFiltersChange: setColumnFilters,
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
columnFilters,
|
columnFilters,
|
||||||
|
columnVisibility,
|
||||||
},
|
},
|
||||||
defaultColumn: {
|
defaultColumn: {
|
||||||
minSize: 0,
|
minSize: 0,
|
||||||
@@ -293,6 +309,47 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="pb-5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
|
||||||
|
<div className="grid md:flex gap-5 w-full items-end">
|
||||||
|
<div className="px-2 sm:px-1">
|
||||||
|
<CardTitle className="mb-2.5">
|
||||||
|
<Trans>All Systems</Trans>
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<Trans>Updated in real time. Click on a system to view information.</Trans>
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 ms-auto w-full md:w-80">
|
||||||
|
<Input placeholder={t`Filter...`} onChange={(e) => setFilter(e.target.value)} className="px-4" />
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline">
|
||||||
|
<Trans comment="Context: table columns">Columns</Trans>{" "}
|
||||||
|
<ChevronDownIcon className="ms-1.5 h-4 w-4 opacity-90" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{table
|
||||||
|
.getAllColumns()
|
||||||
|
.filter((column) => column.getCanHide())
|
||||||
|
.map((column) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={column.id}
|
||||||
|
checked={column.getIsVisible()}
|
||||||
|
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||||
|
>
|
||||||
|
{column.id}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="max-sm:p-2">
|
||||||
<div className="rounded-md border overflow-hidden">
|
<div className="rounded-md border overflow-hidden">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className="bg-muted/40">
|
<TableHeader className="bg-muted/40">
|
||||||
@@ -347,5 +404,7 @@ export default function SystemsTable({ filter }: { filter?: string }) {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user