From 732983493a1bcacfa99874f4bcddc527b92bc2a3 Mon Sep 17 00:00:00 2001 From: henrygd Date: Mon, 20 Apr 2026 21:28:09 -0400 Subject: [PATCH] update --- .../routes/system/network-probes-columns.tsx | 171 -------- .../routes/system/network-probes.tsx | 372 ------------------ 2 files changed, 543 deletions(-) delete mode 100644 internal/site/src/components/routes/system/network-probes-columns.tsx delete mode 100644 internal/site/src/components/routes/system/network-probes.tsx diff --git a/internal/site/src/components/routes/system/network-probes-columns.tsx b/internal/site/src/components/routes/system/network-probes-columns.tsx deleted file mode 100644 index fc15b9d3..00000000 --- a/internal/site/src/components/routes/system/network-probes-columns.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import type { Column, ColumnDef } from "@tanstack/react-table" -import { Button } from "@/components/ui/button" -import { cn, decimalString } from "@/lib/utils" -import { - GlobeIcon, - TagIcon, - TimerIcon, - ActivityIcon, - WifiOffIcon, - Trash2Icon, - ArrowLeftRightIcon, - MoreHorizontalIcon, -} from "lucide-react" -import { t } from "@lingui/core/macro" -import type { NetworkProbeRecord } from "@/types" -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" -import { Trans } from "@lingui/react/macro" - -export interface ProbeRow extends NetworkProbeRecord { - key: string - latency?: number - loss?: number -} - -const protocolColors: Record = { - icmp: "bg-blue-500/15 text-blue-400", - tcp: "bg-purple-500/15 text-purple-400", - http: "bg-green-500/15 text-green-400", -} - -export function getProbeColumns( - deleteProbe: (id: string) => void, - longestName = 0, - longestTarget = 0 -): ColumnDef[] { - return [ - { - id: "name", - sortingFn: (a, b) => (a.original.name || a.original.target).localeCompare(b.original.name || b.original.target), - accessorFn: (record) => record.name || record.target, - header: ({ column }) => , - cell: ({ getValue }) => ( -
- {getValue() as string} -
- ), - }, - { - id: "target", - sortingFn: (a, b) => a.original.target.localeCompare(b.original.target), - accessorFn: (record) => record.target, - header: ({ column }) => , - cell: ({ getValue }) => ( -
- {getValue() as string} -
- ), - }, - { - id: "protocol", - accessorFn: (record) => record.protocol, - header: ({ column }) => , - cell: ({ getValue }) => { - const protocol = getValue() as string - return ( - - {protocol} - - ) - }, - }, - { - id: "interval", - accessorFn: (record) => record.interval, - header: ({ column }) => , - cell: ({ getValue }) => {getValue() as number}s, - }, - { - id: "latency", - accessorFn: (record) => record.latency, - invertSorting: true, - header: ({ column }) => , - cell: ({ row }) => { - const val = row.original.latency - if (val === undefined) { - return - - } - return ( - - 100 ? "bg-yellow-500" : "bg-green-500")} /> - {decimalString(val, val < 100 ? 2 : 1).toLocaleString()} ms - - ) - }, - }, - { - id: "loss", - accessorFn: (record) => record.loss, - invertSorting: true, - header: ({ column }) => , - cell: ({ row }) => { - const val = row.original.loss - if (val === undefined) { - return - - } - return ( - - 0 ? "bg-yellow-500" : "bg-green-500")} /> - {val}% - - ) - }, - }, - { - id: "actions", - enableSorting: false, - header: () => null, - cell: ({ row }) => ( -
- - - - - event.stopPropagation()}> - { - event.stopPropagation() - deleteProbe(row.original.id) - }} - > - - Delete - - - -
- ), - }, - ] -} - -function HeaderButton({ column, name, Icon }: { column: Column; name: string; Icon: React.ElementType }) { - const isSorted = column.getIsSorted() - return ( - - ) -} diff --git a/internal/site/src/components/routes/system/network-probes.tsx b/internal/site/src/components/routes/system/network-probes.tsx deleted file mode 100644 index d3144fc2..00000000 --- a/internal/site/src/components/routes/system/network-probes.tsx +++ /dev/null @@ -1,372 +0,0 @@ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react" -import { Trans, useLingui } from "@lingui/react/macro" -import { pb } from "@/lib/api" -import { useStore } from "@nanostores/react" -import { $chartTime } from "@/lib/stores" -import { chartTimeData, cn, toFixedFloat, decimalString, getVisualStringWidth } from "@/lib/utils" -import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" -import { useToast } from "@/components/ui/use-toast" -import { appendData } from "./chart-data" -// import { AddProbeDialog } from "./probe-dialog" -import { ChartCard } from "./chart-card" -import LineChartDefault, { type DataPoint } from "@/components/charts/line-chart" -import { pinnedAxisDomain } from "@/components/ui/chart" -import type { ChartData, NetworkProbeRecord, NetworkProbeStatsRecord, SystemRecord } from "@/types" -import { - type Row, - type SortingState, - flexRender, - getCoreRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table" -import { useVirtualizer, type VirtualItem } from "@tanstack/react-virtual" -import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" -import { getProbeColumns, type ProbeRow } from "./network-probes-columns" - -function probeKey(p: NetworkProbeRecord) { - if (p.protocol === "tcp") return `${p.protocol}:${p.target}:${p.port}` - return `${p.protocol}:${p.target}` -} - -export default function NetworkProbes({ - system, - chartData, - grid, - realtimeProbeStats, -}: { - system: SystemRecord - chartData: ChartData - grid: boolean - realtimeProbeStats?: NetworkProbeStatsRecord[] -}) { - const systemId = system.id - const [probes, setProbes] = useState([]) - const [stats, setStats] = useState([]) - const [latestResults, setLatestResults] = useState>({}) - const chartTime = useStore($chartTime) - const { toast } = useToast() - const { t } = useLingui() - - const fetchProbes = useCallback(() => { - pb.collection("network_probes") - .getList(0, 2000, { - fields: "id,name,target,protocol,port,interval,enabled,updated", - filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined, - }) - .then((res) => setProbes(res.items)) - .catch(() => setProbes([])) - }, [systemId]) - - useEffect(() => { - fetchProbes() - }, [fetchProbes]) - - // Build set of current probe keys to filter out deleted probes from stats - const activeProbeKeys = useMemo(() => new Set(probes.map(probeKey)), [probes]) - - // Use realtime probe stats when in 1m mode - useEffect(() => { - if (chartTime !== "1m" || !realtimeProbeStats) { - return - } - // Filter stats to only include currently active probes, preserving gap markers - const data: NetworkProbeStatsRecord[] = realtimeProbeStats.map((r) => { - if (!r.stats) { - return r // preserve gap markers from appendData - } - const filtered: NetworkProbeStatsRecord["stats"] = {} - for (const [key, val] of Object.entries(r.stats)) { - if (activeProbeKeys.has(key)) { - filtered[key] = val - } - } - return { stats: filtered, created: r.created } - }) - setStats(data) - // Use last non-gap entry for latest results - for (let i = data.length - 1; i >= 0; i--) { - if (data[i].stats) { - const latest: Record = {} - for (const [key, val] of Object.entries(data[i].stats)) { - latest[key] = { avg: val?.[0], loss: val?.[3] } - } - setLatestResults(latest) - break - } - } - }, [chartTime, realtimeProbeStats, activeProbeKeys]) - - // Fetch probe stats based on chart time (skip in realtime mode) - useEffect(() => { - if (probes.length === 0) { - setStats([]) - setLatestResults({}) - return - } - if (chartTime === "1m") { - return - } - const controller = new AbortController() - const { type: statsType = "1m", expectedInterval } = chartTimeData[chartTime] ?? {} - - console.log("Fetching probe stats", { systemId, statsType, expectedInterval }) - - pb.collection("network_probe_stats") - .getList(0, 2000, { - fields: "stats,created", - filter: pb.filter("system={:system} && type={:type} && created <= {:created}", { - system: systemId, - type: statsType, - created: new Date(Date.now() - 60 * 60 * 1000).toISOString(), - }), - sort: "-created", - }) - .then((raw) => { - console.log("Fetched probe stats", { raw }) - // Filter stats to only include currently active probes - const mapped: NetworkProbeStatsRecord[] = raw.items.map((r) => { - const filtered: NetworkProbeStatsRecord["stats"] = {} - for (const [key, val] of Object.entries(r.stats)) { - if (activeProbeKeys.has(key)) { - filtered[key] = val - } - } - return { stats: filtered, created: new Date(r.created).getTime() } - }) - // Apply gap detection — inserts null markers where data is missing - const data = appendData([] as NetworkProbeStatsRecord[], mapped, expectedInterval) - setStats(data) - if (mapped.length > 0) { - const last = mapped[mapped.length - 1].stats - const latest: Record = {} - for (const [key, val] of Object.entries(last)) { - latest[key] = { avg: val?.[0], loss: val?.[3] } - } - setLatestResults(latest) - } - }) - .catch((e) => { - console.error("Error fetching probe stats", e) - setStats([]) - }) - - return () => controller.abort() - }, [system, chartTime, probes, activeProbeKeys]) - - const deleteProbe = useCallback( - async (id: string) => { - try { - await pb.collection("network_probes").delete(id) - // fetchProbes() - } catch (err: unknown) { - toast({ variant: "destructive", title: t`Error`, description: (err as Error)?.message }) - } - }, - [systemId, t] - ) - - const dataPoints: DataPoint[] = useMemo(() => { - const count = probes.length - return probes.map((p, i) => { - const key = probeKey(p) - return { - label: p.name || p.target, - dataKey: (record: NetworkProbeStatsRecord) => record.stats?.[key]?.[0] ?? null, - color: count <= 5 ? i + 1 : `hsl(${(i * 360) / count}, var(--chart-saturation), var(--chart-lightness))`, - } - }) - }, [probes]) - - const { longestName, longestTarget } = useMemo(() => { - let longestName = 0 - let longestTarget = 0 - for (const p of probes) { - longestName = Math.max(longestName, getVisualStringWidth(p.name || p.target)) - longestTarget = Math.max(longestTarget, getVisualStringWidth(p.target)) - } - return { longestName, longestTarget } - }, [probes]) - - const columns = useMemo( - () => getProbeColumns(deleteProbe, longestName, longestTarget), - [deleteProbe, longestName, longestTarget] - ) - - const tableData: ProbeRow[] = useMemo( - () => - probes.map((p) => { - const key = probeKey(p) - const result = latestResults[key] - return { ...p, key, latency: result?.avg, loss: result?.loss } - }), - [probes, latestResults] - ) - - const [sorting, setSorting] = useState([{ id: "name", desc: false }]) - - const table = useReactTable({ - data: tableData, - columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - onSortingChange: setSorting, - defaultColumn: { - sortUndefined: "last", - size: 100, - minSize: 0, - }, - state: { sorting }, - }) - - const rows = table.getRowModel().rows - const visibleColumns = table.getVisibleLeafColumns() - - // if (probes.length === 0 && stats.length === 0) { - // return ( - // - // - //
- //
- // - // Network Probes - // - // - // ICMP/TCP/HTTP latency monitoring from this agent - // - //
- // {/*
*/} - // - // {/*
*/} - //
- //
- //
- // ) - // } - // - // console.log("Rendering NetworkProbes", { probes, stats }) - - return ( -
- - -
-
- - Network Probes - - - ICMP/TCP/HTTP latency monitoring from this agent - -
- {/* */} -
-
- - -
- - {stats.length > 0 && ( - - `${toFixedFloat(value, value >= 10 ? 0 : 1)} ms`} - contentFormatter={({ value }) => `${decimalString(value, 2)} ms`} - legend - /> - - )} -
- ) -} - -const ProbesTable = memo(function ProbesTable({ - table, - rows, - colLength, -}: { - table: ReturnType> - rows: Row[] - colLength: number -}) { - const scrollRef = useRef(null) - - const virtualizer = useVirtualizer({ - count: rows.length, - estimateSize: () => 54, - getScrollElement: () => scrollRef.current, - overscan: 5, - }) - const virtualRows = virtualizer.getVirtualItems() - - const paddingTop = Math.max(0, virtualRows[0]?.start ?? 0 - virtualizer.options.scrollMargin) - const paddingBottom = Math.max(0, virtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0)) - - return ( -
2) && "min-h-50" - )} - ref={scrollRef} - > -
- - - - {rows.length ? ( - virtualRows.map((virtualRow) => { - const row = rows[virtualRow.index] - return - }) - ) : ( - - - No results. - - - )} - -
-
-
- ) -}) - -function ProbesTableHead({ table }: { table: ReturnType> }) { - return ( - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} - - ))} - - ))} - - ) -} - -const ProbesTableRow = memo(function ProbesTableRow({ - row, - virtualRow, -}: { - row: Row - virtualRow: VirtualItem -}) { - return ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ) -})