mirror of
https://github.com/henrygd/beszel.git
synced 2026-04-13 16:31:49 +02:00
- System page tabs display option - Remove very specific chart components (disk usage, container cpu, etc) and refactor to use more flexible area and line chart components - Optimizations around chart handling to decrease mem usage. Charts are only redrawn now if in view. - Resolve most of the react dev warnings Co-authored-by: sveng93 <svenvanginkel@icloud.com>
117 lines
3.9 KiB
TypeScript
117 lines
3.9 KiB
TypeScript
import { timeTicks } from "d3-time"
|
|
import { getPbTimestamp, pb } from "@/lib/api"
|
|
import { chartTimeData } from "@/lib/utils"
|
|
import type { ChartData, ChartTimes, ContainerStatsRecord, SystemStatsRecord } from "@/types"
|
|
|
|
type ChartTimeData = {
|
|
time: number
|
|
data: {
|
|
ticks: number[]
|
|
domain: number[]
|
|
}
|
|
chartTime: ChartTimes
|
|
}
|
|
|
|
export const cache = new Map<
|
|
string,
|
|
ChartTimeData | SystemStatsRecord[] | ContainerStatsRecord[] | ChartData["containerData"]
|
|
>()
|
|
|
|
// create ticks and domain for charts
|
|
export function getTimeData(chartTime: ChartTimes, lastCreated: number) {
|
|
const cached = cache.get("td") as ChartTimeData | undefined
|
|
if (cached && cached.chartTime === chartTime) {
|
|
if (!lastCreated || cached.time >= lastCreated) {
|
|
return cached.data
|
|
}
|
|
}
|
|
|
|
// const buffer = chartTime === "1m" ? 400 : 20_000
|
|
const now = new Date(Date.now())
|
|
const startTime = chartTimeData[chartTime].getOffset(now)
|
|
const ticks = timeTicks(startTime, now, chartTimeData[chartTime].ticks ?? 12).map((date) => date.getTime())
|
|
const data = {
|
|
ticks,
|
|
domain: [chartTimeData[chartTime].getOffset(now).getTime(), now.getTime()],
|
|
}
|
|
cache.set("td", { time: now.getTime(), data, chartTime })
|
|
return data
|
|
}
|
|
|
|
/** Append new records onto prev with gap detection. Converts string `created` values to ms timestamps in place.
|
|
* Pass `maxLen` to cap the result length in one copy instead of slicing again after the call. */
|
|
export function appendData<T extends { created: string | number | null }>(
|
|
prev: T[],
|
|
newRecords: T[],
|
|
expectedInterval: number,
|
|
maxLen?: number
|
|
): T[] {
|
|
if (!newRecords.length) return prev
|
|
// Pre-trim prev so the single slice() below is the only copy we make
|
|
const trimmed = maxLen && prev.length >= maxLen ? prev.slice(-(maxLen - newRecords.length)) : prev
|
|
const result = trimmed.slice()
|
|
let prevTime = (trimmed.at(-1)?.created as number) ?? 0
|
|
for (const record of newRecords) {
|
|
if (record.created !== null) {
|
|
if (typeof record.created === "string") {
|
|
record.created = new Date(record.created).getTime()
|
|
}
|
|
if (prevTime && (record.created as number) - prevTime > expectedInterval * 1.5) {
|
|
result.push({ created: null, ...("stats" in record ? { stats: null } : {}) } as T)
|
|
}
|
|
prevTime = record.created as number
|
|
}
|
|
result.push(record)
|
|
}
|
|
return result
|
|
}
|
|
|
|
export async function getStats<T extends SystemStatsRecord | ContainerStatsRecord>(
|
|
collection: string,
|
|
systemId: string,
|
|
chartTime: ChartTimes
|
|
): Promise<T[]> {
|
|
const cachedStats = cache.get(`${systemId}_${chartTime}_${collection}`) as T[] | undefined
|
|
const lastCached = cachedStats?.at(-1)?.created as number
|
|
return await pb.collection<T>(collection).getFullList({
|
|
filter: pb.filter("system={:id} && created > {:created} && type={:type}", {
|
|
id: systemId,
|
|
created: getPbTimestamp(chartTime, lastCached ? new Date(lastCached + 1000) : undefined),
|
|
type: chartTimeData[chartTime].type,
|
|
}),
|
|
fields: "created,stats",
|
|
sort: "created",
|
|
})
|
|
}
|
|
|
|
export function makeContainerData(containers: ContainerStatsRecord[]): ChartData["containerData"] {
|
|
const result = [] as ChartData["containerData"]
|
|
for (const { created, stats } of containers) {
|
|
if (!created) {
|
|
result.push({ created: null } as ChartData["containerData"][0])
|
|
continue
|
|
}
|
|
result.push(makeContainerPoint(new Date(created).getTime(), stats))
|
|
}
|
|
return result
|
|
}
|
|
|
|
/** Transform a single realtime container stats message into a ChartDataContainer point. */
|
|
export function makeContainerPoint(
|
|
created: number,
|
|
stats: ContainerStatsRecord["stats"]
|
|
): ChartData["containerData"][0] {
|
|
const point: ChartData["containerData"][0] = { created } as ChartData["containerData"][0]
|
|
for (const container of stats) {
|
|
;(point as Record<string, unknown>)[container.n] = container
|
|
}
|
|
return point
|
|
}
|
|
|
|
export function dockerOrPodman(str: string, isPodman: boolean): string {
|
|
if (isPodman) {
|
|
return str.replace("docker", "podman").replace("Docker", "Podman")
|
|
}
|
|
return str
|
|
}
|