mirror of
https://github.com/henrygd/beszel.git
synced 2025-12-17 10:46:16 +01:00
[Feature] Add Status Filtering to Systems Table (#927)
This commit is contained in:
@@ -36,26 +36,43 @@ import {
|
||||
ArrowUpIcon,
|
||||
Settings2Icon,
|
||||
EyeIcon,
|
||||
PenBoxIcon,
|
||||
ClockIcon,
|
||||
FilterIcon,
|
||||
HourglassIcon,
|
||||
} from "lucide-react"
|
||||
import { memo, useEffect, useMemo, useState } from "react"
|
||||
import { $systems } from "@/lib/stores"
|
||||
import { useStore } from "@nanostores/react"
|
||||
import { cn, useLocalStorage } from "@/lib/utils"
|
||||
import { $router, Link } from "../router"
|
||||
import { useLingui, Trans } from "@lingui/react/macro"
|
||||
import {
|
||||
cn,
|
||||
copyToClipboard,
|
||||
isReadOnlyUser,
|
||||
useLocalStorage,
|
||||
formatTemperature,
|
||||
decimalString,
|
||||
formatBytes,
|
||||
formatUptimeString,
|
||||
} from "@/lib/utils"
|
||||
import AlertsButton from "../alerts/alert-button"
|
||||
import { $router, Link, navigate } from "../router"
|
||||
import { EthernetIcon, GpuIcon, ThermometerIcon } from "../ui/icons"
|
||||
import { useLingui, Trans, Plural } from "@lingui/react/macro"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
|
||||
import { Input } from "../ui/input"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { getPagePath } from "@nanostores/router"
|
||||
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns"
|
||||
import AlertButton from "../alerts/alert-button"
|
||||
import { SystemStatus } from "@/lib/enums"
|
||||
|
||||
type ViewMode = "table" | "grid"
|
||||
type StatusFilter = "all" | "up" | "down" | "paused"
|
||||
|
||||
export default function SystemsTable() {
|
||||
const data = useStore($systems)
|
||||
const { i18n, t } = useLingui()
|
||||
const [filter, setFilter] = useState<string>()
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all")
|
||||
const [sorting, setSorting] = useState<SortingState>([{ id: "system", desc: false }])
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||
const [columnVisibility, setColumnVisibility] = useLocalStorage<VisibilityState>("cols", {})
|
||||
@@ -63,6 +80,14 @@ export default function SystemsTable() {
|
||||
|
||||
const locale = i18n.locale
|
||||
|
||||
// Filter data based on status filter
|
||||
const filteredData = useMemo(() => {
|
||||
if (statusFilter === "all") {
|
||||
return data
|
||||
}
|
||||
return data.filter((system) => system.status === statusFilter)
|
||||
}, [data, statusFilter])
|
||||
|
||||
useEffect(() => {
|
||||
if (filter !== undefined) {
|
||||
table.getColumn("system")?.setFilterValue(filter)
|
||||
@@ -72,7 +97,7 @@ export default function SystemsTable() {
|
||||
const columnDefs = useMemo(() => SystemsTableColumns(viewMode), [viewMode])
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
data: filteredData,
|
||||
columns: columnDefs,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onSortingChange: setSorting,
|
||||
@@ -111,8 +136,8 @@ export default function SystemsTable() {
|
||||
<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" />
|
||||
<div className="flex gap-2 ms-auto w-full md:w-auto">
|
||||
<Input placeholder={t`Filter...`} onChange={(e) => setFilter(e.target.value)} className="px-4 w-full md:w-80" />
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">
|
||||
@@ -121,7 +146,7 @@ export default function SystemsTable() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="h-72 md:h-auto min-w-48 md:min-w-auto overflow-y-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 divide-y md:divide-s md:divide-y-0">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 divide-y md:divide-s md:divide-y-0">
|
||||
<div>
|
||||
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
||||
<LayoutGridIcon className="size-4" />
|
||||
@@ -144,6 +169,32 @@ export default function SystemsTable() {
|
||||
</DropdownMenuRadioGroup>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
||||
<FilterIcon className="size-4" />
|
||||
<Trans>Status</Trans>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup
|
||||
className="px-1 pb-1"
|
||||
value={statusFilter}
|
||||
onValueChange={(value) => setStatusFilter(value as StatusFilter)}
|
||||
>
|
||||
<DropdownMenuRadioItem value="all" onSelect={(e) => e.preventDefault()} className="gap-2">
|
||||
<Trans>All Systems</Trans>
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="up" onSelect={(e) => e.preventDefault()} className="gap-2">
|
||||
<Trans>Up</Trans>
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="down" onSelect={(e) => e.preventDefault()} className="gap-2">
|
||||
<Trans>Down</Trans>
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="paused" onSelect={(e) => e.preventDefault()} className="gap-2">
|
||||
<Trans>Paused</Trans>
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
||||
<ArrowUpDownIcon className="size-4" />
|
||||
@@ -210,7 +261,7 @@ export default function SystemsTable() {
|
||||
</div>
|
||||
</CardHeader>
|
||||
)
|
||||
}, [visibleColumns.length, sorting, viewMode, locale])
|
||||
}, [visibleColumns.length, sorting, viewMode, locale, statusFilter])
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@@ -331,7 +382,7 @@ const SystemCard = memo(
|
||||
>
|
||||
<CardHeader className="py-1 ps-5 pe-3 bg-muted/30 border-b border-border/60">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<CardTitle className="text-base tracking-normal shrink-1 text-primary/90 flex items-center min-h-10 gap-2.5 min-w-0">
|
||||
<CardTitle className="text-base tracking-normal shrink-1 text-primary/90 flex items-center min-w-0 gap-2.5">
|
||||
<div className="flex items-center gap-2.5 min-w-0">
|
||||
<IndicatorDot system={system} />
|
||||
<CardTitle className="text-[.95em]/normal tracking-normal truncate text-primary/90">
|
||||
@@ -356,7 +407,9 @@ const SystemCard = memo(
|
||||
const { Icon, name } = column.columnDef as ColumnDef<SystemRecord, unknown>
|
||||
return (
|
||||
<div key={column.id} className="flex items-center gap-3">
|
||||
{Icon && <Icon className="size-4 text-muted-foreground" />}
|
||||
{column.id === "lastSeen" ? (
|
||||
<EyeIcon className="size-4 text-muted-foreground" />
|
||||
) : Icon && <Icon className="size-4 text-muted-foreground" />}
|
||||
<div className="flex items-center gap-3 flex-1">
|
||||
<span className="text-muted-foreground min-w-16">{name()}:</span>
|
||||
<div className="flex-1">{flexRender(cell.column.columnDef.cell, cell.getContext())}</div>
|
||||
|
||||
Reference in New Issue
Block a user