mirror of
https://github.com/henrygd/beszel.git
synced 2026-05-06 10:51:50 +02:00
update
This commit is contained in:
@@ -31,6 +31,12 @@ func bindNetworkProbesEvents(hub *Hub) {
|
||||
if !e.Record.GetBool("enabled") {
|
||||
return nil
|
||||
}
|
||||
// if system connected, run the probe immediately
|
||||
// if not, return and wait for the system to connect and sync probes then
|
||||
system, err := hub.sm.GetSystem(e.Record.GetString("system"))
|
||||
if err != nil || system.Status != "up" {
|
||||
return nil
|
||||
}
|
||||
result, err := hub.upsertNetworkProbe(e.Record, true)
|
||||
if err != nil {
|
||||
hub.Logger().Warn("failed to sync probe to agent", "system", e.Record.GetString("system"), "probe", e.Record.Id, "err", err)
|
||||
|
||||
@@ -111,6 +111,8 @@ export default function AreaChartDefault({
|
||||
})
|
||||
}, [areasKey, displayMaxToggled])
|
||||
|
||||
const XAxis = xAxis(chartData.chartTime, displayData.at(-1)?.created)
|
||||
|
||||
return useMemo(() => {
|
||||
if (displayData.length === 0) {
|
||||
return null
|
||||
@@ -146,7 +148,7 @@ export default function AreaChartDefault({
|
||||
axisLine={false}
|
||||
/>
|
||||
)}
|
||||
{xAxis(chartData.chartTime, displayData.at(-1)?.created as number)}
|
||||
{XAxis}
|
||||
<ChartTooltip
|
||||
animationEasing="ease-out"
|
||||
animationDuration={150}
|
||||
@@ -167,5 +169,5 @@ export default function AreaChartDefault({
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
)
|
||||
}, [displayData, yAxisWidth, filter, Areas])
|
||||
}, [displayData, yAxisWidth, filter, Areas, XAxis])
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export default function LineChartDefault({
|
||||
truncate = false,
|
||||
chartProps,
|
||||
connectNulls,
|
||||
dot = false,
|
||||
}: {
|
||||
chartData: ChartData
|
||||
// biome-ignore lint/suspicious/noExplicitAny: accepts different data source types (systemStats or containerData)
|
||||
@@ -64,6 +65,7 @@ export default function LineChartDefault({
|
||||
truncate?: boolean
|
||||
chartProps?: Omit<React.ComponentProps<typeof LineChart>, "data" | "margin">
|
||||
connectNulls?: boolean
|
||||
dot?: boolean
|
||||
}) {
|
||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||
const { isIntersecting, ref } = useIntersectionObserver({ freeze: false })
|
||||
@@ -87,6 +89,8 @@ export default function LineChartDefault({
|
||||
// Use a stable key derived from data point identities and visual properties
|
||||
const linesKey = dataPoints?.map((d) => `${d.label}:${d.strokeOpacity ?? ""}`).join("\0")
|
||||
|
||||
const XAxis = xAxis(chartData.chartTime, displayData.at(-1)?.created)
|
||||
|
||||
const Lines = useMemo(() => {
|
||||
return dataPoints?.map((dataPoint, i) => {
|
||||
let { color } = dataPoint
|
||||
@@ -99,7 +103,7 @@ export default function LineChartDefault({
|
||||
dataKey={dataPoint.dataKey}
|
||||
name={dataPoint.label}
|
||||
type="monotoneX"
|
||||
dot={false}
|
||||
dot={dot}
|
||||
strokeWidth={1.5}
|
||||
stroke={color}
|
||||
strokeOpacity={dataPoint.strokeOpacity}
|
||||
@@ -148,7 +152,7 @@ export default function LineChartDefault({
|
||||
axisLine={false}
|
||||
/>
|
||||
)}
|
||||
{xAxis(chartData.chartTime, displayData.at(-1)?.created as number)}
|
||||
{XAxis}
|
||||
<ChartTooltip
|
||||
animationEasing="ease-out"
|
||||
animationDuration={150}
|
||||
@@ -169,5 +173,5 @@ export default function LineChartDefault({
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
)
|
||||
}, [displayData, yAxisWidth, filter, Lines])
|
||||
}, [displayData, yAxisWidth, filter, Lines, XAxis])
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react"
|
||||
import { useRef, useState } from "react"
|
||||
import { Trans, useLingui } from "@lingui/react/macro"
|
||||
import { useStore } from "@nanostores/react"
|
||||
import { pb } from "@/lib/api"
|
||||
@@ -111,13 +111,14 @@ export function AddProbeDialog({ systemId }: { systemId?: string }) {
|
||||
const [bulkInput, setBulkInput] = useState("")
|
||||
const [bulkLoading, setBulkLoading] = useState(false)
|
||||
const [bulkSelectedSystemId, setBulkSelectedSystemId] = useState("")
|
||||
const bulkFormRef = useRef<HTMLFormElement>(null)
|
||||
const { toast } = useToast()
|
||||
const { t } = useLingui()
|
||||
const systems = useStore($systems)
|
||||
|
||||
const resetBulkForm = () => {
|
||||
setBulkInput("")
|
||||
setBulkSelectedSystemId("")
|
||||
// setBulkSelectedSystemId("")
|
||||
}
|
||||
|
||||
const openBulkAdd = (selectedSystemId?: string) => {
|
||||
@@ -140,13 +141,15 @@ export function AddProbeDialog({ systemId }: { systemId?: string }) {
|
||||
|
||||
try {
|
||||
const system = systemId ?? bulkSelectedSystemId
|
||||
if (!system) {
|
||||
throw new Error("Select a system.")
|
||||
}
|
||||
const rawLines = bulkInput.split(/\r?\n/).filter((line) => line.trim())
|
||||
if (!rawLines.length) {
|
||||
throw new Error("Enter at least one probe.")
|
||||
}
|
||||
|
||||
const payloads = rawLines.map((line, index) => parseBulkProbeLine(line, index + 1, system))
|
||||
setBulkOpen(false)
|
||||
closedForSubmit = true
|
||||
let batch = pb.createBatch()
|
||||
let inBatch = 0
|
||||
@@ -221,16 +224,10 @@ export function AddProbeDialog({ systemId }: { systemId?: string }) {
|
||||
<Trans>Bulk Add {{ foo: t`Network Probes` }}</Trans>
|
||||
</SheetTitle>
|
||||
<SheetDescription>
|
||||
<Trans>
|
||||
Paste one probe per line. See{" "}
|
||||
<a href={"#bulk-add-probes-docs"} className="underline underline-offset-2">
|
||||
the documentation
|
||||
</a>
|
||||
.
|
||||
</Trans>
|
||||
target[,protocol[,port[,interval[,name]]]] - TCP/HTTP default to port 443.
|
||||
</SheetDescription>
|
||||
</SheetHeader>
|
||||
<form onSubmit={handleBulkSubmit} className="flex h-full flex-col overflow-hidden">
|
||||
<form ref={bulkFormRef} onSubmit={handleBulkSubmit} className="flex h-full flex-col overflow-hidden">
|
||||
<div className="flex-1 space-y-4 overflow-auto p-4">
|
||||
{!systemId && (
|
||||
<div className="grid gap-2">
|
||||
@@ -252,8 +249,8 @@ export function AddProbeDialog({ systemId }: { systemId?: string }) {
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="bulk-probes">
|
||||
<Trans>Entries</Trans>
|
||||
<Label htmlFor="bulk-probes" className="sr-only">
|
||||
Entries
|
||||
</Label>
|
||||
<Textarea
|
||||
id="bulk-probes"
|
||||
@@ -262,10 +259,10 @@ export function AddProbeDialog({ systemId }: { systemId?: string }) {
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault()
|
||||
handleBulkSubmit(e)
|
||||
bulkFormRef.current?.requestSubmit()
|
||||
}
|
||||
}}
|
||||
className="h-120 font-mono text-sm bg-muted/40"
|
||||
className="h-200 font-mono text-sm bg-muted/40"
|
||||
style={{ maxHeight: `calc(100vh - 20rem)` }}
|
||||
placeholder={["1.1.1.1", "example.com,tcp", "https://example.com,http,,60,Homepage"].join("\n")}
|
||||
required
|
||||
@@ -338,8 +335,12 @@ function ProbeDialogContent({
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
const selectedSystem = systemId ?? selectedSystemId
|
||||
if (!selectedSystem) {
|
||||
throw new Error("Select a system.")
|
||||
}
|
||||
const payload = buildProbePayload({
|
||||
system: systemId ?? selectedSystemId,
|
||||
system: selectedSystem,
|
||||
target,
|
||||
protocol,
|
||||
port: protocol === "tcp" ? Number(port) : 0,
|
||||
|
||||
@@ -97,6 +97,7 @@ function ProbeChart({
|
||||
contentFormatter={contentFormatter}
|
||||
legend={legend}
|
||||
filter={filter}
|
||||
dot={chartData.chartTime === "1m"}
|
||||
/>
|
||||
</ChartCard>
|
||||
)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { JSX } from "react"
|
||||
import { useLingui } from "@lingui/react/macro"
|
||||
import * as React from "react"
|
||||
import * as RechartsPrimitive from "recharts"
|
||||
import { chartTimeData, cn } from "@/lib/utils"
|
||||
import type { ChartTimes } from "@/types"
|
||||
import { Separator } from "./separator"
|
||||
import { AxisDomain } from "recharts/types/util/types"
|
||||
import type { AxisDomain } from "recharts/types/util/types"
|
||||
import { timeTicks } from "d3-time"
|
||||
|
||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||
@@ -102,7 +101,7 @@ const ChartTooltipContent = React.forwardRef<
|
||||
labelKey?: string
|
||||
unit?: string
|
||||
filter?: string
|
||||
contentFormatter?: (item: any, key: string) => React.ReactNode | string
|
||||
contentFormatter?: (item: unknown, key: string) => React.ReactNode | string
|
||||
truncate?: boolean
|
||||
showTotal?: boolean
|
||||
totalLabel?: React.ReactNode
|
||||
@@ -176,7 +175,13 @@ const ChartTooltipContent = React.forwardRef<
|
||||
}
|
||||
|
||||
const totalKey = "__total__"
|
||||
const totalItem: any = {
|
||||
const totalItem: {
|
||||
value: number
|
||||
name: string
|
||||
dataKey: string
|
||||
color: string | undefined
|
||||
payload?: unknown
|
||||
} = {
|
||||
value: totalValue,
|
||||
name: totalName,
|
||||
dataKey: totalKey,
|
||||
@@ -401,21 +406,23 @@ function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key:
|
||||
return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]
|
||||
}
|
||||
|
||||
let cachedAxis: {
|
||||
time: number
|
||||
el: JSX.Element
|
||||
interface XAxisData {
|
||||
el: React.ReactElement
|
||||
domain: [number, number]
|
||||
}
|
||||
|
||||
const xAxis = (chartTime: ChartTimes, lastCreationTime: number) => {
|
||||
if (Math.abs(lastCreationTime - cachedAxis?.time) < 1000) {
|
||||
return cachedAxis.el
|
||||
}
|
||||
const now = new Date(lastCreationTime + 1000)
|
||||
const startTime = chartTimeData[chartTime].getOffset(now)
|
||||
const ticks = timeTicks(startTime, now, chartTimeData[chartTime].ticks ?? 12).map((date) => date.getTime())
|
||||
const domain = [chartTimeData[chartTime].getOffset(now).getTime(), now.getTime()]
|
||||
cachedAxis = {
|
||||
time: lastCreationTime,
|
||||
const xAxisCache = new Map<ChartTimes, XAxisData>()
|
||||
|
||||
function createXAxisData(chartTime: ChartTimes): XAxisData {
|
||||
// console.log("Creating XAxis for", chartTime, new Date())
|
||||
const axisEndTime = Date.now() + 500
|
||||
const axisEndDate = new Date(axisEndTime)
|
||||
const startTime = chartTimeData[chartTime].getOffset(axisEndDate)
|
||||
const ticks = timeTicks(startTime, axisEndDate, chartTimeData[chartTime].ticks ?? 12).map((date) => date.getTime())
|
||||
const domain: [number, number] = [startTime.getTime(), axisEndTime]
|
||||
|
||||
return {
|
||||
domain,
|
||||
el: (
|
||||
<RechartsPrimitive.XAxis
|
||||
dataKey="created"
|
||||
@@ -431,7 +438,25 @@ const xAxis = (chartTime: ChartTimes, lastCreationTime: number) => {
|
||||
/>
|
||||
),
|
||||
}
|
||||
return cachedAxis.el
|
||||
}
|
||||
|
||||
function xAxis(chartTime: ChartTimes, lastCreated: number) {
|
||||
if (!lastCreated) {
|
||||
return null
|
||||
}
|
||||
const cachedAxis = xAxisCache.get(chartTime)
|
||||
|
||||
const expectedInterval = chartTimeData[chartTime].expectedInterval
|
||||
const conservativeEndTime = Date.now() - expectedInterval / 2
|
||||
const axisEndTime = Math.max(lastCreated, conservativeEndTime)
|
||||
|
||||
if (cachedAxis && axisEndTime < cachedAxis.domain[1]) {
|
||||
return cachedAxis.el
|
||||
}
|
||||
|
||||
const axisData = createXAxisData(chartTime)
|
||||
xAxisCache.set(chartTime, axisData)
|
||||
return axisData.el
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -41,7 +41,7 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b border-border/60 hover:bg-muted/40 dark:hover:bg-muted/20 data-[state=selected]:bg-muted/40",
|
||||
"border-b border-border/60 hover:bg-muted/40 dark:hover:bg-muted/20 data-[state=selected]:bg-muted/40!",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -224,7 +224,7 @@ export function useNetworkProbesData(props: UseNetworkProbesProps) {
|
||||
// if no previous data or the last data point is older than 1min,
|
||||
// create a new data set starting with a point 1 second ago to seed the chart data
|
||||
if (!prev || (prev.at(-1)?.created ?? 0) < now - 60_000) {
|
||||
prev = [{ created: now - 1000, stats: probesToStats(probes) }]
|
||||
prev = [{ created: now - 2000, stats: probesToStats(probes) }]
|
||||
}
|
||||
const stats = { created: now, stats: data.Probes } as NetworkProbeStatsRecord
|
||||
const newStats = appendData(prev, [stats], 1000, 120)
|
||||
@@ -248,6 +248,7 @@ export function useNetworkProbesData(props: UseNetworkProbesProps) {
|
||||
function probesToStats(probes: NetworkProbeRecord[]): NetworkProbeStatsRecord["stats"] {
|
||||
const stats: NetworkProbeStatsRecord["stats"] = {}
|
||||
for (const probe of probes) {
|
||||
// TODO: include only if probe.updated < charttime
|
||||
stats[probe.id] = [probe.res, probe.resAvg1h, probe.resMin1h, probe.resMax1h, probe.loss1h]
|
||||
}
|
||||
return stats
|
||||
|
||||
Reference in New Issue
Block a user