mirror of
https://github.com/henrygd/beszel.git
synced 2026-05-06 10:51:50 +02:00
updates
This commit is contained in:
@@ -8,6 +8,29 @@ import type { RecordListOptions, RecordSubscription } from "pocketbase"
|
||||
|
||||
const cache = new Map<string, NetworkProbeStatsRecord[]>()
|
||||
|
||||
function getCacheValue(systemId: string, chartTime: ChartTimes | "rt") {
|
||||
return cache.get(`${systemId}${chartTime}`) || []
|
||||
}
|
||||
|
||||
function appendCacheValue(
|
||||
systemId: string,
|
||||
chartTime: ChartTimes | "rt",
|
||||
newStats: NetworkProbeStatsRecord[],
|
||||
maxPoints = 100
|
||||
) {
|
||||
const cache_key = `${systemId}${chartTime}`
|
||||
const existingStats = getCacheValue(systemId, chartTime)
|
||||
if (existingStats) {
|
||||
const { expectedInterval } = chartTimeData[chartTime]
|
||||
const updatedStats = appendData(existingStats, newStats, expectedInterval, maxPoints)
|
||||
cache.set(cache_key, updatedStats)
|
||||
return updatedStats
|
||||
} else {
|
||||
cache.set(cache_key, newStats)
|
||||
return newStats
|
||||
}
|
||||
}
|
||||
|
||||
const NETWORK_PROBE_FIELDS = "id,name,system,target,protocol,port,interval,latency,loss,enabled,updated"
|
||||
|
||||
interface UseNetworkProbesProps {
|
||||
@@ -91,25 +114,83 @@ export function useNetworkProbesData(props: UseNetworkProbesProps) {
|
||||
}
|
||||
}, [systemId])
|
||||
|
||||
// fetch probe stats when probes update
|
||||
// Subscribe to new probe stats
|
||||
useEffect(() => {
|
||||
if (!loadStats || !systemId) {
|
||||
return
|
||||
}
|
||||
let unsubscribe: (() => void) | undefined
|
||||
const pbOptions = {
|
||||
fields: "stats,created,type",
|
||||
filter: pb.filter("system = {:system}", { system: systemId }),
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
unsubscribe = await pb.collection<NetworkProbeStatsRecord>("network_probe_stats").subscribe(
|
||||
"*",
|
||||
(event) => {
|
||||
if (!chartTime || event.action !== "create") {
|
||||
return
|
||||
}
|
||||
// if (typeof event.record.created === "string") {
|
||||
// event.record.created = new Date(event.record.created).getTime()
|
||||
// }
|
||||
// return if not current chart time
|
||||
// we could append to other chart times, but we would need to check the timestamps
|
||||
// to make sure they fit in correctly, so for simplicity just ignore non-chart-time updates
|
||||
// and fetch them via API when the user switches to that chart time
|
||||
const chartTimeRecordType = chartTimeData[chartTime].type as ChartTimes
|
||||
if (event.record.type !== chartTimeRecordType) {
|
||||
// const lastCreated = getCacheValue(systemId, chartTime)?.at(-1)?.created ?? 0
|
||||
// if (lastCreated) {
|
||||
// // if the new record is close enough to the last cached record, append it to the cache so it's available immediately if the user switches to that chart time
|
||||
// const { expectedInterval } = chartTimeData[chartTime]
|
||||
// if (event.record.created - lastCreated < expectedInterval * 1.5) {
|
||||
// console.log(
|
||||
// `Caching out-of-chart-time probe stats record for chart time ${chartTime} (record type: ${event.record.type})`
|
||||
// )
|
||||
// const newStats = appendCacheValue(systemId, chartTime, [event.record])
|
||||
// cache.set(`${systemId}${chartTime}`, newStats)
|
||||
// }
|
||||
// }
|
||||
// console.log(`Received probe stats for non-current chart time (${event.record.type}), ignoring for now`)
|
||||
return
|
||||
}
|
||||
|
||||
// console.log("Appending new probe stats to chart:", event.record)
|
||||
const newStats = appendCacheValue(systemId, chartTime, [event.record])
|
||||
setProbeStats(newStats)
|
||||
},
|
||||
pbOptions
|
||||
)
|
||||
} catch (error) {
|
||||
console.error("Failed to subscribe to probe stats:", error)
|
||||
}
|
||||
})()
|
||||
|
||||
return () => unsubscribe?.()
|
||||
}, [systemId])
|
||||
|
||||
// fetch missing probe stats on load and when chart time changes
|
||||
useEffect(() => {
|
||||
if (!loadStats || !systemId || !chartTime || chartTime === "1m") {
|
||||
return
|
||||
}
|
||||
|
||||
const { expectedInterval } = chartTimeData[chartTime]
|
||||
const cache_key = `${systemId}${chartTime}`
|
||||
const requestId = ++statsRequestId.current
|
||||
|
||||
const cachedProbeStats = cache.get(cache_key) as NetworkProbeStatsRecord[] | undefined
|
||||
const cachedProbeStats = getCacheValue(systemId, chartTime)
|
||||
|
||||
// Render from cache immediately if available
|
||||
if (cachedProbeStats?.length) {
|
||||
if (cachedProbeStats.length) {
|
||||
setProbeStats(cachedProbeStats)
|
||||
|
||||
// Skip the fetch if the latest cached point is recent enough that no new point is expected yet
|
||||
const lastCreated = cachedProbeStats.at(-1)?.created
|
||||
if (lastCreated && Date.now() - lastCreated < expectedInterval * 0.9) {
|
||||
console.log("Using cached probe stats, skipping fetch")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -120,17 +201,42 @@ export function useNetworkProbesData(props: UseNetworkProbesProps) {
|
||||
if (requestId !== statsRequestId.current) {
|
||||
return
|
||||
}
|
||||
|
||||
// make new system stats
|
||||
let probeStatsData = (cache.get(cache_key) || []) as NetworkProbeStatsRecord[]
|
||||
if (probeStats.length) {
|
||||
probeStatsData = appendData(probeStatsData, probeStats, expectedInterval, 100)
|
||||
cache.set(cache_key, probeStatsData)
|
||||
}
|
||||
setProbeStats(probeStatsData)
|
||||
const newStats = appendCacheValue(systemId, chartTime, probeStats)
|
||||
setProbeStats(newStats)
|
||||
}
|
||||
)
|
||||
}, [chartTime, probes])
|
||||
}, [chartTime])
|
||||
|
||||
// subscribe to realtime metrics if chart time is 1m
|
||||
useEffect(() => {
|
||||
if (!loadStats || !systemId || chartTime !== "1m") {
|
||||
return
|
||||
}
|
||||
let unsubscribe: (() => void) | undefined
|
||||
const cache_key = `${systemId}rt`
|
||||
pb.realtime
|
||||
.subscribe(
|
||||
`rt_metrics`,
|
||||
(data: { Probes: NetworkProbeStatsRecord["stats"] }) => {
|
||||
let prev = getCacheValue(systemId, "rt")
|
||||
const now = Date.now()
|
||||
// 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) }]
|
||||
}
|
||||
const stats = { created: now, stats: data.Probes } as NetworkProbeStatsRecord
|
||||
const newStats = appendData(prev, [stats], 1000, 120)
|
||||
setProbeStats(() => newStats)
|
||||
cache.set(cache_key, newStats)
|
||||
},
|
||||
{ query: { system: systemId } }
|
||||
)
|
||||
.then((us) => {
|
||||
unsubscribe = us
|
||||
})
|
||||
return () => unsubscribe?.()
|
||||
}, [chartTime, systemId])
|
||||
|
||||
return {
|
||||
probes,
|
||||
@@ -138,6 +244,20 @@ export function useNetworkProbesData(props: UseNetworkProbesProps) {
|
||||
}
|
||||
}
|
||||
|
||||
export function probeKey(p: NetworkProbeRecord) {
|
||||
if (p.protocol === "tcp") return `${p.protocol}:${p.target}:${p.port}`
|
||||
return `${p.protocol}:${p.target}`
|
||||
}
|
||||
|
||||
function probesToStats(probes: NetworkProbeRecord[]): NetworkProbeStatsRecord["stats"] {
|
||||
const stats: NetworkProbeStatsRecord["stats"] = {}
|
||||
for (const probe of probes) {
|
||||
const key = probeKey(probe)
|
||||
stats[key] = [probe.latency, 0, 0, probe.loss]
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
async function fetchProbes(systemId?: string) {
|
||||
try {
|
||||
const res = await pb.collection<NetworkProbeRecord>("network_probes").getList(0, 2000, {
|
||||
|
||||
Reference in New Issue
Block a user