mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-25 02:47:46 +02:00
feat(ui): Add view settings to network monitors table (#2396)
This commit is contained in:
@@ -23,7 +23,7 @@ import { Input } from "@/components/ui/input"
|
|||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
import { ChevronDownIcon, ListIcon, SearchIcon, ServerIcon } from "lucide-react"
|
import { ChevronDownIcon, ListIcon, PlusIcon, SearchIcon, ServerIcon } from "lucide-react"
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
import { $systems } from "@/lib/stores"
|
import { $systems } from "@/lib/stores"
|
||||||
import { cn, supportsNetworkMonitors } from "@/lib/utils"
|
import { cn, supportsNetworkMonitors } from "@/lib/utils"
|
||||||
@@ -446,8 +446,13 @@ export function AddMonitorDialog({ systemId, monitors }: { systemId?: string; mo
|
|||||||
<>
|
<>
|
||||||
<div className="flex gap-0 rounded-lg">
|
<div className="flex gap-0 rounded-lg">
|
||||||
<Button variant="outline" onClick={openAdd} className="rounded-e-none grow" disabled={!hasEligibleSystems}>
|
<Button variant="outline" onClick={openAdd} className="rounded-e-none grow" disabled={!hasEligibleSystems}>
|
||||||
{/* <PlusIcon className="size-4 me-1" /> */}
|
<PlusIcon className="size-4 me-1" />
|
||||||
<Trans>Add {{ foo: t`Monitor` }}</Trans>
|
<span className="sm:hidden">
|
||||||
|
<Trans>Add</Trans>
|
||||||
|
</span>
|
||||||
|
<span className="hidden sm:inline">
|
||||||
|
<Trans>Add {{ foo: t`Monitor` }}</Trans>
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
<div className="w-px h-full bg-muted"></div>
|
<div className="w-px h-full bg-muted"></div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ import { Badge } from "../ui/badge"
|
|||||||
import { getMonitorTarget } from "@/lib/network-monitor-utils"
|
import { getMonitorTarget } from "@/lib/network-monitor-utils"
|
||||||
import { pb } from "@/lib/api"
|
import { pb } from "@/lib/api"
|
||||||
|
|
||||||
|
declare module "@tanstack/react-table" {
|
||||||
|
interface ColumnMeta<TData, TValue> {
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const protocolColors: Record<string, string> = {
|
const protocolColors: Record<string, string> = {
|
||||||
icmp: "bg-blue-500/15! text-blue-600 dark:text-blue-400",
|
icmp: "bg-blue-500/15! text-blue-600 dark:text-blue-400",
|
||||||
tcp: "bg-purple-500/15! text-purple-600 dark:text-purple-400",
|
tcp: "bg-purple-500/15! text-purple-600 dark:text-purple-400",
|
||||||
@@ -98,6 +104,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system",
|
id: "system",
|
||||||
|
meta: { label: t`System` },
|
||||||
accessorFn: (record) => record.system,
|
accessorFn: (record) => record.system,
|
||||||
sortingFn: (a, b) => {
|
sortingFn: (a, b) => {
|
||||||
const allSystems = $allSystemsById.get()
|
const allSystems = $allSystemsById.get()
|
||||||
@@ -134,6 +141,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "target",
|
id: "target",
|
||||||
|
meta: { label: t`Target` },
|
||||||
sortingFn: (a, b) => a.original.target.localeCompare(b.original.target),
|
sortingFn: (a, b) => a.original.target.localeCompare(b.original.target),
|
||||||
accessorFn: (record) => getMonitorTarget(record),
|
accessorFn: (record) => getMonitorTarget(record),
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Target`} Icon={GlobeIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Target`} Icon={GlobeIcon} />,
|
||||||
@@ -164,6 +172,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "protocol",
|
id: "protocol",
|
||||||
|
meta: { label: t`Protocol` },
|
||||||
accessorFn: (record) => record.protocol,
|
accessorFn: (record) => record.protocol,
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Protocol`} Icon={ArrowLeftRightIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Protocol`} Icon={ArrowLeftRightIcon} />,
|
||||||
cell: ({ getValue }) => {
|
cell: ({ getValue }) => {
|
||||||
@@ -173,6 +182,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "interval",
|
id: "interval",
|
||||||
|
meta: { label: t`Interval` },
|
||||||
accessorFn: (record) => record.interval,
|
accessorFn: (record) => record.interval,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Interval`} Icon={RefreshCwIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Interval`} Icon={RefreshCwIcon} />,
|
||||||
@@ -180,6 +190,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "res",
|
id: "res",
|
||||||
|
meta: { label: t`Response` },
|
||||||
accessorFn: (record) => record.res,
|
accessorFn: (record) => record.res,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Response`} Icon={TimerIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Response`} Icon={TimerIcon} />,
|
||||||
@@ -187,6 +198,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "res1h",
|
id: "res1h",
|
||||||
|
meta: { label: t`Avg 1h` },
|
||||||
accessorFn: (record) => record.resAvg1h,
|
accessorFn: (record) => record.resAvg1h,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Avg 1h`} Icon={TimerIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Avg 1h`} Icon={TimerIcon} />,
|
||||||
@@ -194,6 +206,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "max1h",
|
id: "max1h",
|
||||||
|
meta: { label: t`Max 1h` },
|
||||||
accessorFn: (record) => record.resMax1h,
|
accessorFn: (record) => record.resMax1h,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Max 1h`} Icon={TimerIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Max 1h`} Icon={TimerIcon} />,
|
||||||
@@ -201,6 +214,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "min1h",
|
id: "min1h",
|
||||||
|
meta: { label: t`Min 1h` },
|
||||||
accessorFn: (record) => record.resMin1h,
|
accessorFn: (record) => record.resMin1h,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Min 1h`} Icon={TimerIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Min 1h`} Icon={TimerIcon} />,
|
||||||
@@ -208,6 +222,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "loss",
|
id: "loss",
|
||||||
|
meta: { label: t`Loss 1h` },
|
||||||
accessorFn: (record) => record.loss1h,
|
accessorFn: (record) => record.loss1h,
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Loss 1h`} Icon={WifiOffIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Loss 1h`} Icon={WifiOffIcon} />,
|
||||||
@@ -236,6 +251,7 @@ export function getMonitorColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "updated",
|
id: "updated",
|
||||||
|
meta: { label: t`Updated` },
|
||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
accessorFn: (record) => record.updated,
|
accessorFn: (record) => record.updated,
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Updated`} Icon={ClockIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Updated`} Icon={ClockIcon} />,
|
||||||
|
|||||||
@@ -26,27 +26,47 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog"
|
} from "@/components/ui/alert-dialog"
|
||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
import { memo, useCallback, useMemo, useRef, useState } from "react"
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
|
import { subscribeKeys } from "nanostores"
|
||||||
import { getMonitorColumns } from "@/components/network-monitors-table/network-monitors-columns"
|
import { getMonitorColumns } from "@/components/network-monitors-table/network-monitors-columns"
|
||||||
import { Card, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
import { isReadOnlyUser } from "@/lib/api"
|
import { isReadOnlyUser, queueUserSettings } from "@/lib/api"
|
||||||
import { pb } from "@/lib/api"
|
import { pb } from "@/lib/api"
|
||||||
import { SystemStatus } from "@/lib/enums"
|
import { SystemStatus } from "@/lib/enums"
|
||||||
import { $allSystemsById, $direction, getUserChartTime } from "@/lib/stores"
|
import { $allSystemsById, $direction, $userSettings, getUserChartTime } from "@/lib/stores"
|
||||||
import {
|
import {
|
||||||
cn,
|
cn,
|
||||||
isVisuallyLonger,
|
isVisuallyLonger,
|
||||||
matchesFilterGroups,
|
matchesFilterGroups,
|
||||||
parseFilterGroups,
|
parseFilterGroups,
|
||||||
parseSemVer,
|
parseSemVer,
|
||||||
useBrowserStorage,
|
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
import type { ChartData, NetworkMonitorRecord } from "@/types"
|
import type { ChartData, NetworkMonitorRecord } from "@/types"
|
||||||
import { AddMonitorDialog, EditMonitorDialog } from "./monitor-dialog"
|
import { AddMonitorDialog, EditMonitorDialog } from "./monitor-dialog"
|
||||||
import { ArrowLeftRightIcon, EthernetPortIcon, LoaderCircleIcon, ServerIcon, XIcon } from "lucide-react"
|
import {
|
||||||
|
ArrowDownIcon,
|
||||||
|
ArrowLeftRightIcon,
|
||||||
|
ArrowUpDownIcon,
|
||||||
|
ArrowUpIcon,
|
||||||
|
EthernetPortIcon,
|
||||||
|
EyeIcon,
|
||||||
|
LoaderCircleIcon,
|
||||||
|
ServerIcon,
|
||||||
|
Settings2Icon,
|
||||||
|
XIcon,
|
||||||
|
} from "lucide-react"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
|
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
|
||||||
import ChartTimeSelect from "@/components/charts/chart-time-select"
|
import ChartTimeSelect from "@/components/charts/chart-time-select"
|
||||||
import { LossChart, AvgMinMaxResponseChart } from "@/components/routes/system/charts/monitors-charts"
|
import { LossChart, AvgMinMaxResponseChart } from "@/components/routes/system/charts/monitors-charts"
|
||||||
@@ -66,13 +86,19 @@ export default function NetworkMonitorsTableNew({
|
|||||||
monitors: NetworkMonitorRecord[]
|
monitors: NetworkMonitorRecord[]
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
}) {
|
}) {
|
||||||
const [sorting, setSorting] = useBrowserStorage<SortingState>(
|
const sortSettingsKey = systemId ? "monitorSortModeSystem" : "monitorSortMode"
|
||||||
`sort-np-target-${systemId ? 1 : 0}`,
|
const sortStorageKey = `besz-sort-np-target-${systemId ? 1 : 0}`
|
||||||
[{ id: systemId ? "target" : "system", desc: false }],
|
const [sorting, setSorting] = useState<SortingState>(
|
||||||
sessionStorage
|
() =>
|
||||||
|
$userSettings.get()[sortSettingsKey] ??
|
||||||
|
JSON.parse(sessionStorage.getItem(sortStorageKey) || "null") ?? [
|
||||||
|
{ id: systemId ? "target" : "system", desc: false },
|
||||||
|
]
|
||||||
)
|
)
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
|
||||||
|
() => $userSettings.get().monitorCols ?? JSON.parse(localStorage.getItem("besz-monitor-cols") || "{}")
|
||||||
|
)
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||||
const [globalFilter, setGlobalFilter] = useState("")
|
const [globalFilter, setGlobalFilter] = useState("")
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false)
|
const [deleteOpen, setDeleteOpen] = useState(false)
|
||||||
@@ -82,6 +108,47 @@ export default function NetworkMonitorsTableNew({
|
|||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const canManageMonitors = !isReadOnlyUser()
|
const canManageMonitors = !isReadOnlyUser()
|
||||||
|
|
||||||
|
// Apply settings from server once they load (handles incognito / new devices)
|
||||||
|
const appliedSettings = useRef(new Set<string>())
|
||||||
|
useEffect(() => {
|
||||||
|
return subscribeKeys($userSettings, ["monitorCols", sortSettingsKey], (vals) => {
|
||||||
|
if (!appliedSettings.current.has("monitorCols") && vals.monitorCols !== undefined) {
|
||||||
|
appliedSettings.current.add("monitorCols")
|
||||||
|
setColumnVisibility(vals.monitorCols)
|
||||||
|
}
|
||||||
|
if (!appliedSettings.current.has(sortSettingsKey) && vals[sortSettingsKey] !== undefined) {
|
||||||
|
appliedSettings.current.add(sortSettingsKey)
|
||||||
|
setSorting(vals[sortSettingsKey] as SortingState)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [sortSettingsKey])
|
||||||
|
|
||||||
|
const handleColumnVisibilityChange = useCallback(
|
||||||
|
(updater: VisibilityState | ((prev: VisibilityState) => VisibilityState)) => {
|
||||||
|
setColumnVisibility((prev) => {
|
||||||
|
const next = typeof updater === "function" ? updater(prev) : updater
|
||||||
|
localStorage.setItem("besz-monitor-cols", JSON.stringify(next))
|
||||||
|
$userSettings.setKey("monitorCols", next)
|
||||||
|
queueUserSettings({ monitorCols: next })
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSortingChange = useCallback(
|
||||||
|
(updater: SortingState | ((prev: SortingState) => SortingState)) => {
|
||||||
|
setSorting((prev) => {
|
||||||
|
const next = typeof updater === "function" ? updater(prev) : updater
|
||||||
|
sessionStorage.setItem(sortStorageKey, JSON.stringify(next))
|
||||||
|
$userSettings.setKey(sortSettingsKey, next)
|
||||||
|
queueUserSettings({ [sortSettingsKey]: next })
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[sortSettingsKey, sortStorageKey]
|
||||||
|
)
|
||||||
|
|
||||||
const longestTarget = useMemo(() => {
|
const longestTarget = useMemo(() => {
|
||||||
let longestTarget = ""
|
let longestTarget = ""
|
||||||
for (const p of monitors) {
|
for (const p of monitors) {
|
||||||
@@ -208,9 +275,9 @@ export default function NetworkMonitorsTableNew({
|
|||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
onSortingChange: setSorting,
|
onSortingChange: handleSortingChange,
|
||||||
onColumnFiltersChange: setColumnFilters,
|
onColumnFiltersChange: setColumnFilters,
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
onColumnVisibilityChange: handleColumnVisibilityChange,
|
||||||
onRowSelectionChange: setRowSelection,
|
onRowSelectionChange: setRowSelection,
|
||||||
defaultColumn: {
|
defaultColumn: {
|
||||||
sortUndefined: "last",
|
sortUndefined: "last",
|
||||||
@@ -237,11 +304,12 @@ export default function NetworkMonitorsTableNew({
|
|||||||
|
|
||||||
const rows = table.getRowModel().rows
|
const rows = table.getRowModel().rows
|
||||||
const visibleColumns = table.getVisibleLeafColumns()
|
const visibleColumns = table.getVisibleLeafColumns()
|
||||||
|
const visibleColumnsKey = visibleColumns.map((column) => column.id).join(",")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="@container w-full px-3 py-5 sm:py-6 sm:px-6">
|
<Card className="@container w-full px-3 py-5 sm:py-6 sm:px-6">
|
||||||
<CardHeader className="p-0 mb-3 sm:mb-4">
|
<CardHeader className="p-0 mb-3 sm:mb-4">
|
||||||
<div className="grid md:flex gap-x-5 gap-y-3 w-full items-end">
|
<div className="grid md-lg:flex gap-x-5 gap-y-3 w-full items-end">
|
||||||
<div className="px-2 sm:px-1">
|
<div className="px-2 sm:px-1">
|
||||||
<CardTitle className="mb-2">
|
<CardTitle className="mb-2">
|
||||||
<Trans>Network Monitors</Trans>
|
<Trans>Network Monitors</Trans>
|
||||||
@@ -250,14 +318,14 @@ export default function NetworkMonitorsTableNew({
|
|||||||
<Trans>Response time monitoring from agents.</Trans>
|
<Trans>Response time monitoring from agents.</Trans>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:ms-auto flex items-center gap-2">
|
<div className="md-lg:ms-auto flex items-center gap-2">
|
||||||
{monitors.length > 0 && (
|
{monitors.length > 0 && (
|
||||||
<div className="relative">
|
<div className="relative grow">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t`Filter...`}
|
placeholder={t`Filter...`}
|
||||||
value={globalFilter}
|
value={globalFilter}
|
||||||
onChange={(e) => setGlobalFilter(e.target.value)}
|
onChange={(e) => setGlobalFilter(e.target.value)}
|
||||||
className="ms-auto px-4 w-full max-w-full md:w-50"
|
className="ms-auto px-4 w-full max-w-full md-lg:w-50"
|
||||||
/>
|
/>
|
||||||
{globalFilter && (
|
{globalFilter && (
|
||||||
<Button
|
<Button
|
||||||
@@ -273,6 +341,74 @@ export default function NetworkMonitorsTableNew({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline">
|
||||||
|
<Settings2Icon className="me-1.5 size-4 opacity-80" />
|
||||||
|
<Trans>View</Trans>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="h-72 md:h-auto min-w-48 md:min-w-auto overflow-y-auto">
|
||||||
|
<div className="grid grid-cols-2 divide-y md:divide-s md:divide-y-0">
|
||||||
|
<div className="border-r">
|
||||||
|
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
||||||
|
<ArrowUpDownIcon className="size-4" />
|
||||||
|
<Trans>Sort By</Trans>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<div className="px-1 pb-1">
|
||||||
|
{table.getAllColumns().map((column) => {
|
||||||
|
if (!column.getCanSort()) return null
|
||||||
|
let Icon = <span className="w-6"></span>
|
||||||
|
if (sorting[0]?.id === column.id) {
|
||||||
|
Icon = sorting[0]?.desc ? (
|
||||||
|
<ArrowUpIcon className="me-2 size-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowDownIcon className="me-2 size-4" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onSelect={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
handleSortingChange([
|
||||||
|
{ id: column.id, desc: sorting[0]?.id === column.id && !sorting[0]?.desc },
|
||||||
|
])
|
||||||
|
}}
|
||||||
|
key={column.id}
|
||||||
|
>
|
||||||
|
{Icon}
|
||||||
|
{column.columnDef.meta?.label ?? column.id}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<DropdownMenuLabel className="pt-2 px-3.5 flex items-center gap-2">
|
||||||
|
<EyeIcon className="size-4" />
|
||||||
|
<Trans>Visible Fields</Trans>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<div className="px-1.5 pb-1">
|
||||||
|
{table
|
||||||
|
.getAllColumns()
|
||||||
|
.filter((column) => column.getCanHide())
|
||||||
|
.map((column) => (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={column.id}
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
checked={column.getIsVisible()}
|
||||||
|
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||||
|
>
|
||||||
|
{column.columnDef.meta?.label ?? column.id}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
{canManageMonitors ? <AddMonitorDialog systemId={systemId} monitors={monitors} /> : null}
|
{canManageMonitors ? <AddMonitorDialog systemId={systemId} monitors={monitors} /> : null}
|
||||||
{canManageMonitors ? (
|
{canManageMonitors ? (
|
||||||
<EditMonitorDialog
|
<EditMonitorDialog
|
||||||
@@ -325,6 +461,7 @@ export default function NetworkMonitorsTableNew({
|
|||||||
table={table}
|
table={table}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
colLength={visibleColumns.length}
|
colLength={visibleColumns.length}
|
||||||
|
visibleColumnsKey={visibleColumnsKey}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
@@ -337,12 +474,14 @@ const NetworkMonitorsTable = memo(function NetworkMonitorTable({
|
|||||||
table,
|
table,
|
||||||
rows,
|
rows,
|
||||||
colLength,
|
colLength,
|
||||||
|
visibleColumnsKey,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
isLoading,
|
isLoading,
|
||||||
}: {
|
}: {
|
||||||
table: TableType<NetworkMonitorRecord>
|
table: TableType<NetworkMonitorRecord>
|
||||||
rows: Row<NetworkMonitorRecord>[]
|
rows: Row<NetworkMonitorRecord>[]
|
||||||
colLength: number
|
colLength: number
|
||||||
|
visibleColumnsKey: string
|
||||||
rowSelection: RowSelectionState
|
rowSelection: RowSelectionState
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
}) {
|
}) {
|
||||||
@@ -390,6 +529,7 @@ const NetworkMonitorsTable = memo(function NetworkMonitorTable({
|
|||||||
virtualRow={virtualRow}
|
virtualRow={virtualRow}
|
||||||
isSelected={row.getIsSelected()}
|
isSelected={row.getIsSelected()}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
|
visibleColumnsKey={visibleColumnsKey}
|
||||||
openSheet={openSheet}
|
openSheet={openSheet}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -442,6 +582,9 @@ const NetworkMonitorTableRow = memo(function NetworkMonitorTableRow({
|
|||||||
virtualRow,
|
virtualRow,
|
||||||
isSelected,
|
isSelected,
|
||||||
rowSelection: _rowSelection,
|
rowSelection: _rowSelection,
|
||||||
|
// Column visibility doesn't change the row object identity, so this prop exists only
|
||||||
|
// to force a re-render (and a fresh row.getVisibleCells() read) when columns are toggled.
|
||||||
|
visibleColumnsKey: _visibleColumnsKey,
|
||||||
openSheet,
|
openSheet,
|
||||||
}: {
|
}: {
|
||||||
row: Row<NetworkMonitorRecord>
|
row: Row<NetworkMonitorRecord>
|
||||||
@@ -449,6 +592,7 @@ const NetworkMonitorTableRow = memo(function NetworkMonitorTableRow({
|
|||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
// Menus depend on the entire selection, including changes to other rows.
|
// Menus depend on the entire selection, including changes to other rows.
|
||||||
rowSelection: RowSelectionState
|
rowSelection: RowSelectionState
|
||||||
|
visibleColumnsKey: string
|
||||||
openSheet: (monitor: NetworkMonitorRecord) => void
|
openSheet: (monitor: NetworkMonitorRecord) => void
|
||||||
}) {
|
}) {
|
||||||
const system = useStore($allSystemsById)[row.original.system]
|
const system = useStore($allSystemsById)[row.original.system]
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
|
|
||||||
--breakpoint-xs: 26.6rem;
|
--breakpoint-xs: 26.6rem;
|
||||||
--breakpoint-450: 28rem;
|
--breakpoint-450: 28rem;
|
||||||
|
--breakpoint-md-lg: 53rem;
|
||||||
--breakpoint-2xl: 90rem;
|
--breakpoint-2xl: 90rem;
|
||||||
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
@@ -117,6 +118,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|
||||||
/* Fonts */
|
/* Fonts */
|
||||||
@supports (font-variation-settings: normal) {
|
@supports (font-variation-settings: normal) {
|
||||||
:root {
|
:root {
|
||||||
@@ -172,6 +174,7 @@
|
|||||||
@utility scrollbar-hide {
|
@utility scrollbar-hide {
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
3
internal/site/src/types.d.ts
vendored
3
internal/site/src/types.d.ts
vendored
@@ -377,6 +377,9 @@ export interface UserSettings {
|
|||||||
statusFilter?: "all" | "up" | "down" | "paused" | "pending"
|
statusFilter?: "all" | "up" | "down" | "paused" | "pending"
|
||||||
viewMode?: "table" | "grid"
|
viewMode?: "table" | "grid"
|
||||||
sortMode?: Array<{ id: string; desc: boolean }>
|
sortMode?: Array<{ id: string; desc: boolean }>
|
||||||
|
monitorCols?: Record<string, boolean>
|
||||||
|
monitorSortMode?: Array<{ id: string; desc: boolean }>
|
||||||
|
monitorSortModeSystem?: Array<{ id: string; desc: boolean }>
|
||||||
grid?: boolean
|
grid?: boolean
|
||||||
displayMode?: "default" | "tabs"
|
displayMode?: "default" | "tabs"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user