diff --git a/internal/site/src/components/network-monitors-table/monitor-dialog.tsx b/internal/site/src/components/network-monitors-table/monitor-dialog.tsx
index a227ef20..565c0eb8 100644
--- a/internal/site/src/components/network-monitors-table/monitor-dialog.tsx
+++ b/internal/site/src/components/network-monitors-table/monitor-dialog.tsx
@@ -23,7 +23,7 @@ import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
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 { $systems } from "@/lib/stores"
import { cn, supportsNetworkMonitors } from "@/lib/utils"
@@ -446,8 +446,13 @@ export function AddMonitorDialog({ systemId, monitors }: { systemId?: string; mo
<>
diff --git a/internal/site/src/components/network-monitors-table/network-monitors-columns.tsx b/internal/site/src/components/network-monitors-table/network-monitors-columns.tsx
index 92d25aba..f0c4af0d 100644
--- a/internal/site/src/components/network-monitors-table/network-monitors-columns.tsx
+++ b/internal/site/src/components/network-monitors-table/network-monitors-columns.tsx
@@ -40,6 +40,12 @@ import { Badge } from "../ui/badge"
import { getMonitorTarget } from "@/lib/network-monitor-utils"
import { pb } from "@/lib/api"
+declare module "@tanstack/react-table" {
+ interface ColumnMeta {
+ label?: string
+ }
+}
+
const protocolColors: Record = {
icmp: "bg-blue-500/15! text-blue-600 dark:text-blue-400",
tcp: "bg-purple-500/15! text-purple-600 dark:text-purple-400",
@@ -98,6 +104,7 @@ export function getMonitorColumns(
},
{
id: "system",
+ meta: { label: t`System` },
accessorFn: (record) => record.system,
sortingFn: (a, b) => {
const allSystems = $allSystemsById.get()
@@ -134,6 +141,7 @@ export function getMonitorColumns(
},
{
id: "target",
+ meta: { label: t`Target` },
sortingFn: (a, b) => a.original.target.localeCompare(b.original.target),
accessorFn: (record) => getMonitorTarget(record),
header: ({ column }) => ,
@@ -164,6 +172,7 @@ export function getMonitorColumns(
},
{
id: "protocol",
+ meta: { label: t`Protocol` },
accessorFn: (record) => record.protocol,
header: ({ column }) => ,
cell: ({ getValue }) => {
@@ -173,6 +182,7 @@ export function getMonitorColumns(
},
{
id: "interval",
+ meta: { label: t`Interval` },
accessorFn: (record) => record.interval,
invertSorting: true,
header: ({ column }) => ,
@@ -180,6 +190,7 @@ export function getMonitorColumns(
},
{
id: "res",
+ meta: { label: t`Response` },
accessorFn: (record) => record.res,
invertSorting: true,
header: ({ column }) => ,
@@ -187,6 +198,7 @@ export function getMonitorColumns(
},
{
id: "res1h",
+ meta: { label: t`Avg 1h` },
accessorFn: (record) => record.resAvg1h,
invertSorting: true,
header: ({ column }) => ,
@@ -194,6 +206,7 @@ export function getMonitorColumns(
},
{
id: "max1h",
+ meta: { label: t`Max 1h` },
accessorFn: (record) => record.resMax1h,
invertSorting: true,
header: ({ column }) => ,
@@ -201,6 +214,7 @@ export function getMonitorColumns(
},
{
id: "min1h",
+ meta: { label: t`Min 1h` },
accessorFn: (record) => record.resMin1h,
invertSorting: true,
header: ({ column }) => ,
@@ -208,6 +222,7 @@ export function getMonitorColumns(
},
{
id: "loss",
+ meta: { label: t`Loss 1h` },
accessorFn: (record) => record.loss1h,
invertSorting: true,
header: ({ column }) => ,
@@ -236,6 +251,7 @@ export function getMonitorColumns(
},
{
id: "updated",
+ meta: { label: t`Updated` },
invertSorting: true,
accessorFn: (record) => record.updated,
header: ({ column }) => ,
diff --git a/internal/site/src/components/network-monitors-table/network-monitors-table.tsx b/internal/site/src/components/network-monitors-table/network-monitors-table.tsx
index 4f381795..78f81efb 100644
--- a/internal/site/src/components/network-monitors-table/network-monitors-table.tsx
+++ b/internal/site/src/components/network-monitors-table/network-monitors-table.tsx
@@ -26,27 +26,47 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
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 { Card, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { useToast } from "@/components/ui/use-toast"
-import { isReadOnlyUser } from "@/lib/api"
+import { isReadOnlyUser, queueUserSettings } from "@/lib/api"
import { pb } from "@/lib/api"
import { SystemStatus } from "@/lib/enums"
-import { $allSystemsById, $direction, getUserChartTime } from "@/lib/stores"
+import { $allSystemsById, $direction, $userSettings, getUserChartTime } from "@/lib/stores"
import {
cn,
isVisuallyLonger,
matchesFilterGroups,
parseFilterGroups,
parseSemVer,
- useBrowserStorage,
} from "@/lib/utils"
import type { ChartData, NetworkMonitorRecord } from "@/types"
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 ChartTimeSelect from "@/components/charts/chart-time-select"
import { LossChart, AvgMinMaxResponseChart } from "@/components/routes/system/charts/monitors-charts"
@@ -66,13 +86,19 @@ export default function NetworkMonitorsTableNew({
monitors: NetworkMonitorRecord[]
isLoading: boolean
}) {
- const [sorting, setSorting] = useBrowserStorage(
- `sort-np-target-${systemId ? 1 : 0}`,
- [{ id: systemId ? "target" : "system", desc: false }],
- sessionStorage
+ const sortSettingsKey = systemId ? "monitorSortModeSystem" : "monitorSortMode"
+ const sortStorageKey = `besz-sort-np-target-${systemId ? 1 : 0}`
+ const [sorting, setSorting] = useState(
+ () =>
+ $userSettings.get()[sortSettingsKey] ??
+ JSON.parse(sessionStorage.getItem(sortStorageKey) || "null") ?? [
+ { id: systemId ? "target" : "system", desc: false },
+ ]
)
const [columnFilters, setColumnFilters] = useState([])
- const [columnVisibility, setColumnVisibility] = useState({})
+ const [columnVisibility, setColumnVisibility] = useState(
+ () => $userSettings.get().monitorCols ?? JSON.parse(localStorage.getItem("besz-monitor-cols") || "{}")
+ )
const [rowSelection, setRowSelection] = useState({})
const [globalFilter, setGlobalFilter] = useState("")
const [deleteOpen, setDeleteOpen] = useState(false)
@@ -82,6 +108,47 @@ export default function NetworkMonitorsTableNew({
const { toast } = useToast()
const canManageMonitors = !isReadOnlyUser()
+ // Apply settings from server once they load (handles incognito / new devices)
+ const appliedSettings = useRef(new Set())
+ 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(() => {
let longestTarget = ""
for (const p of monitors) {
@@ -208,9 +275,9 @@ export default function NetworkMonitorsTableNew({
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
- onSortingChange: setSorting,
+ onSortingChange: handleSortingChange,
onColumnFiltersChange: setColumnFilters,
- onColumnVisibilityChange: setColumnVisibility,
+ onColumnVisibilityChange: handleColumnVisibilityChange,
onRowSelectionChange: setRowSelection,
defaultColumn: {
sortUndefined: "last",
@@ -237,11 +304,12 @@ export default function NetworkMonitorsTableNew({
const rows = table.getRowModel().rows
const visibleColumns = table.getVisibleLeafColumns()
+ const visibleColumnsKey = visibleColumns.map((column) => column.id).join(",")
return (
-
+
Network Monitors
@@ -250,14 +318,14 @@ export default function NetworkMonitorsTableNew({
Response time monitoring from agents.
-
+
{monitors.length > 0 && (
-
+
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 && (