From 027159420c00a9a0404275e077b13dee33b3f198 Mon Sep 17 00:00:00 2001 From: henrygd Date: Fri, 24 Apr 2026 01:50:17 -0400 Subject: [PATCH] update --- internal/hub/probes.go | 13 +- .../network-probes-columns.tsx | 182 +++++++++++++----- .../network-probes-table.tsx | 32 +-- 3 files changed, 142 insertions(+), 85 deletions(-) diff --git a/internal/hub/probes.go b/internal/hub/probes.go index dd35a5d8..e338a1c0 100644 --- a/internal/hub/probes.go +++ b/internal/hub/probes.go @@ -62,16 +62,19 @@ func bindNetworkProbesEvents(hub *Hub) { return nil } err := e.Next() - if err != nil { - return err - } if e.Record.GetBool("enabled") { - _, err = hub.upsertNetworkProbe(e.Record, false) + var result *probe.Result + runNow := !e.Record.Original().GetBool("enabled") + result, err = hub.upsertNetworkProbe(e.Record, runNow) + if result != nil { + setProbeResultFields(e.Record, *result) + _ = e.App.SaveNoValidate(e.Record) + } } else { err = hub.deleteNetworkProbe(e.Record) } if err != nil { - hub.Logger().Warn("failed to sync updated probe to agent", "system", e.Record.GetString("system"), "probe", e.Record.Id, "err", err) + hub.Logger().Warn("failed to sync updated probe", "system", e.Record.GetString("system"), "probe", e.Record.Id, "err", err) } return nil }) diff --git a/internal/site/src/components/network-probes-table/network-probes-columns.tsx b/internal/site/src/components/network-probes-table/network-probes-columns.tsx index c44d203a..f5c5a5e0 100644 --- a/internal/site/src/components/network-probes-table/network-probes-columns.tsx +++ b/internal/site/src/components/network-probes-table/network-probes-columns.tsx @@ -13,9 +13,11 @@ import { NetworkIcon, RefreshCwIcon, PenBoxIcon, + PauseCircleIcon, + PlayCircleIcon, } from "lucide-react" import { t } from "@lingui/core/macro" -import type { NetworkProbeRecord } from "@/types" +import type { NetworkProbeRecord, SystemRecord } from "@/types" import { DropdownMenu, DropdownMenuContent, @@ -25,9 +27,12 @@ import { } from "@/components/ui/dropdown-menu" import { Trans } from "@lingui/react/macro" import { pb } from "@/lib/api" -import { toast } from "../ui/use-toast" +import { toast } from "@/components/ui/use-toast" import { $allSystemsById } from "@/lib/stores" import { useStore } from "@nanostores/react" +import { SystemStatus } from "@/lib/enums" +import { Checkbox } from "@/components/ui/checkbox" +import { useMemo } from "react" const protocolColors: Record = { icmp: "bg-blue-500/15 text-blue-400", @@ -43,12 +48,54 @@ async function deleteProbe(id: string) { } } +async function setProbeEnabled(id: string, enabled: boolean) { + try { + await pb.collection("network_probes").update(id, { enabled }) + } catch (err: unknown) { + toast({ variant: "destructive", title: t`Error`, description: (err as Error)?.message }) + } +} + +const STATUS_COLORS = { + [SystemStatus.Up]: "bg-green-500", + [SystemStatus.Down]: "bg-red-500", + [SystemStatus.Paused]: "bg-primary/40", + [SystemStatus.Pending]: "bg-yellow-500", +} as const + +/** + * A probe is considered muted if it's disabled or if its associated system is not up. + */ +const isMuted = (record: NetworkProbeRecord, systemRecord: SystemRecord | undefined) => + !record.enabled || systemRecord?.status !== SystemStatus.Up + export function getProbeColumns( longestName = 0, longestTarget = 0, onEdit?: (probe: NetworkProbeRecord) => void ): ColumnDef[] { return [ + { + id: "select", + header: ({ table }) => ( + table.toggleAllRowsSelected(!!value)} + aria-label={t`Select all`} + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label={t`Select row`} + /> + ), + enableSorting: false, + enableHiding: false, + size: 44, + }, { id: "name", sortingFn: (a, b) => (a.original.name || a.original.target).localeCompare(b.original.name || b.original.target), @@ -71,8 +118,19 @@ export function getProbeColumns( }, header: ({ column }) => , cell: ({ getValue }) => { - const allSystems = useStore($allSystemsById) - return {allSystems[getValue() as string]?.name ?? ""} + const system = useStore($allSystemsById)[getValue() as string] as SystemRecord | undefined + const name = system?.name + const status = system?.status as SystemStatus // undefined val is fine but makes lsp mad + + return useMemo( + () => ( + + + {name} + + ), + [status, name] + ) }, }, { @@ -139,12 +197,18 @@ export function getProbeColumns( invertSorting: true, header: ({ column }) => , cell: ({ row }) => { - const { loss1h, res } = row.original + const { loss1h, res, system } = row.original + const systemRecord = useStore($allSystemsById)[system] + if (loss1h === undefined || (!res && !loss1h)) { return - } + + const muted = isMuted(row.original, systemRecord) let color = "bg-green-500" - if (loss1h) { + if (muted) { + color = "bg-muted-foreground/50" + } else if (loss1h) { color = loss1h > 20 ? "bg-red-500" : "bg-yellow-500" } return ( @@ -171,64 +235,82 @@ export function getProbeColumns( enableHiding: false, header: () => null, size: 40, - cell: ({ row }) => ( - - - - - event.stopPropagation()}> - { - event.stopPropagation() - onEdit?.(row.original) - }} - > - - Edit - - - { - event.stopPropagation() - deleteProbe(row.original.id) - }} - > - - Delete - - - - ), + cell: ({ row }) => { + const { enabled } = row.original + return ( + + + + + event.stopPropagation()}> + onEdit?.(row.original)}> + + Edit + + setProbeEnabled(row.original.id, !enabled)}> + {enabled ? ( + <> + + Pause + + ) : ( + <> + + Resume + + )} + + + { + event.stopPropagation() + deleteProbe(row.original.id) + }} + > + + Delete + + + + ) + }, }, ] } function responseTimeCell(cell: CellContext) { - const val = cell.getValue() as number | undefined - if (!val) { + const probe = cell.row.original + const systemRecord = useStore($allSystemsById)[probe.system] + const responseTime = cell.getValue() as number | undefined + + if (!responseTime) { return - } + + const muted = isMuted(probe, systemRecord) let color = "bg-green-500" - if (val > 200) { + if (muted) { + color = "bg-muted-foreground/50" + } else if (responseTime > 200) { color = "bg-yellow-500" } - if (val > 2000) { + if (!muted && responseTime > 2000) { color = "bg-red-500" } return ( - {decimalString(val, val < 100 ? 2 : 1).toLocaleString()}ms + {decimalString(responseTime, responseTime < 100 ? 2 : 1).toLocaleString()}ms ) } diff --git a/internal/site/src/components/network-probes-table/network-probes-table.tsx b/internal/site/src/components/network-probes-table/network-probes-table.tsx index 91bd0eff..c001ef40 100644 --- a/internal/site/src/components/network-probes-table/network-probes-table.tsx +++ b/internal/site/src/components/network-probes-table/network-probes-table.tsx @@ -2,7 +2,6 @@ import { t } from "@lingui/core/macro" import { Trans } from "@lingui/react/macro" import { type ColumnFiltersState, - type ColumnDef, flexRender, getCoreRowModel, getFilteredRowModel, @@ -30,7 +29,6 @@ import { Button, buttonVariants } from "@/components/ui/button" import { memo, useMemo, useRef, useState } from "react" import { getProbeColumns } from "@/components/network-probes-table/network-probes-columns" import { Card, CardHeader, CardTitle } from "@/components/ui/card" -import { Checkbox } from "@/components/ui/checkbox" import { Input } from "@/components/ui/input" import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { useToast } from "@/components/ui/use-toast" @@ -78,34 +76,8 @@ export default function NetworkProbesTableNew({ let columns = getProbeColumns(longestName, longestTarget, setEditingProbe) columns = systemId ? columns.filter((col) => col.id !== "system") : columns columns = canManageProbes ? columns : columns.filter((col) => col.id !== "actions") - if (!canManageProbes) { - return columns - } - - const selectionColumn: ColumnDef = { - id: "select", - header: ({ table }) => ( - table.toggleAllRowsSelected(!!value)} - aria-label={t`Select all`} - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label={t`Select row`} - /> - ), - enableSorting: false, - enableHiding: false, - size: 44, - } - - return [selectionColumn, ...columns] - }, [systemId, longestName, longestTarget, canManageProbes]) + return columns + }, [systemId, longestName, longestTarget]) const handleBulkDelete = async () => { setDeleteOpen(false)