refactor: js performance improvements

This commit is contained in:
henrygd
2025-08-06 22:21:48 -04:00
parent de56544ca3
commit 12059ee3db
5 changed files with 79 additions and 65 deletions

1
.gitignore vendored
View File

@@ -19,3 +19,4 @@ beszel/site/src/locales/**/*.ts
__debug_* __debug_*
beszel/internal/agent/lhm/obj beszel/internal/agent/lhm/obj
beszel/internal/agent/lhm/bin beszel/internal/agent/lhm/bin
dockerfile_agent_dev

View File

@@ -29,41 +29,36 @@ export default memo(function ContainerChart({
const isNetChart = chartType === ChartType.Network const isNetChart = chartType === ChartType.Network
const chartConfig = useMemo(() => { const chartConfig = useMemo(() => {
let config = {} as Record< const config = {} as Record<string, { label: string; color: string }>
string, const totalUsage = new Map<string, number>()
{
label: string // calculate total usage of each container
color: string for (const stats of containerData) {
} for (const key in stats) {
> if (!key || key === "created") continue
const totalUsage = {} as Record<string, number>
for (let stats of containerData) { const currentTotal = totalUsage.get(key) ?? 0
for (let key in stats) { const increment = isNetChart
if (!key || key === "created") { ? (stats[key]?.nr ?? 0) + (stats[key]?.ns ?? 0)
continue : // @ts-ignore
} stats[key]?.[dataKey] ?? 0
if (!(key in totalUsage)) {
totalUsage[key] = 0 totalUsage.set(key, currentTotal + increment)
}
if (isNetChart) {
totalUsage[key] += (stats[key]?.nr ?? 0) + (stats[key]?.ns ?? 0)
} else {
// @ts-ignore
totalUsage[key] += stats[key]?.[dataKey] ?? 0
}
} }
} }
let keys = Object.keys(totalUsage)
keys.sort((a, b) => (totalUsage[a] > totalUsage[b] ? -1 : 1)) // Sort keys and generate colors based on usage
const length = keys.length const sortedEntries = Array.from(totalUsage.entries()).sort(([, a], [, b]) => b - a)
for (let i = 0; i < length; i++) {
const key = keys[i] const length = sortedEntries.length
sortedEntries.forEach(([key], i) => {
const hue = ((i * 360) / length) % 360 const hue = ((i * 360) / length) % 360
config[key] = { config[key] = {
label: key, label: key,
color: `hsl(${hue}, 60%, 55%)`, color: `hsl(${hue}, 60%, 55%)`,
} }
} })
return config satisfies ChartConfig return config satisfies ChartConfig
}, [chartData]) }, [chartData])
@@ -124,6 +119,8 @@ export default memo(function ContainerChart({
return obj return obj
}, []) }, [])
const filterLower = filter?.toLowerCase()
// console.log('rendered at', new Date()) // console.log('rendered at', new Date())
if (containerData.length === 0) { if (containerData.length === 0) {
@@ -165,7 +162,7 @@ export default memo(function ContainerChart({
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />} content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />}
/> />
{Object.keys(chartConfig).map((key) => { {Object.keys(chartConfig).map((key) => {
const filtered = filter && !key.toLowerCase().includes(filter.toLowerCase()) const filtered = filterLower && !key.toLowerCase().includes(filterLower)
let fillOpacity = filtered ? 0.05 : 0.4 let fillOpacity = filtered ? 0.05 : 0.4
let strokeOpacity = filtered ? 0.1 : 1 let strokeOpacity = filtered ? 0.1 : 1
return ( return (

View File

@@ -56,16 +56,18 @@ import { buttonVariants } from "../ui/button"
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { MeterState } from "@/lib/enums" import { MeterState } from "@/lib/enums"
const STATUS_COLORS = {
up: "bg-green-500",
down: "bg-red-500",
paused: "bg-primary/40",
pending: "bg-yellow-500",
} as const
/** /**
* @param viewMode - "table" or "grid" * @param viewMode - "table" or "grid"
* @returns - Column definitions for the systems table * @returns - Column definitions for the systems table
*/ */
export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] { export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] {
const statusTranslations = {
up: () => t`Up`.toLowerCase(),
down: () => t`Down`.toLowerCase(),
paused: () => t`Paused`.toLowerCase(),
}
return [ return [
{ {
size: 200, size: 200,
@@ -73,18 +75,35 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
accessorKey: "name", accessorKey: "name",
id: "system", id: "system",
name: () => t`System`, name: () => t`System`,
filterFn: (row, _, filterVal) => { filterFn: (() => {
const filterLower = filterVal.toLowerCase() let filterInput = ""
const { name, status } = row.original let filterInputLower = ""
// Check if the filter matches the name or status for this row const nameCache = new Map<string, string>()
if ( const statusTranslations = {
name.toLowerCase().includes(filterLower) || up: t`Up`.toLowerCase(),
statusTranslations[status as keyof typeof statusTranslations]?.().includes(filterLower) down: t`Down`.toLowerCase(),
) { paused: t`Paused`.toLowerCase(),
return true } as const
// match filter value against name or translated status
return (row, _, newFilterInput) => {
const { name, status } = row.original
if (newFilterInput !== filterInput) {
filterInput = newFilterInput
filterInputLower = newFilterInput.toLowerCase()
}
let nameLower = nameCache.get(name)
if (nameLower === undefined) {
nameLower = name.toLowerCase()
nameCache.set(name, nameLower)
}
if (nameLower.includes(filterInputLower)) {
return true
}
const statusLower = statusTranslations[status as keyof typeof statusTranslations]
return statusLower?.includes(filterInputLower) || false
} }
return false })(),
},
enableHiding: false, enableHiding: false,
invertSorting: false, invertSorting: false,
Icon: ServerIcon, Icon: ServerIcon,
@@ -166,9 +185,9 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
<div className="flex items-center gap-[.35em] w-full tabular-nums tracking-tight"> <div className="flex items-center gap-[.35em] w-full tabular-nums tracking-tight">
<span <span
className={cn("inline-block size-2 rounded-full me-0.5", { className={cn("inline-block size-2 rounded-full me-0.5", {
"bg-green-500": threshold === MeterState.Good, [STATUS_COLORS.up]: threshold === MeterState.Good,
"bg-yellow-500": threshold === MeterState.Warn, [STATUS_COLORS.pending]: threshold === MeterState.Warn,
"bg-red-600": threshold === MeterState.Crit, [STATUS_COLORS.down]: threshold === MeterState.Crit,
})} })}
/> />
{loadAverages?.map((la, i) => ( {loadAverages?.map((la, i) => (
@@ -190,7 +209,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
if (sys.status === "paused") { if (sys.status === "paused") {
return null return null
} }
const userSettings = useStore($userSettings) const userSettings = useStore($userSettings, { keys: ["unitNet"] })
const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false) const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false)
return ( return (
<span className="tabular-nums whitespace-nowrap"> <span className="tabular-nums whitespace-nowrap">
@@ -212,7 +231,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
if (!val) { if (!val) {
return null return null
} }
const userSettings = useStore($userSettings) const userSettings = useStore($userSettings, { keys: ["unitTemp"] })
const { value, unit } = formatTemperature(val, userSettings.unitTemp) const { value, unit } = formatTemperature(val, userSettings.unitTemp)
return ( return (
<span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-0.5")}> <span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-0.5")}>
@@ -241,9 +260,9 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
<IndicatorDot <IndicatorDot
system={system} system={system}
className={ className={
(system.status !== "up" && "bg-primary/30") || (system.status !== "up" && STATUS_COLORS.paused) ||
(version === globalThis.BESZEL.HUB_VERSION && "bg-green-500") || (version === globalThis.BESZEL.HUB_VERSION && STATUS_COLORS.up) ||
"bg-yellow-500" STATUS_COLORS.pending
} }
/> />
<span className="truncate max-w-14">{info.getValue() as string}</span> <span className="truncate max-w-14">{info.getValue() as string}</span>
@@ -293,10 +312,10 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
<span <span
className={cn( className={cn(
"absolute inset-0 w-full h-full origin-left", "absolute inset-0 w-full h-full origin-left",
(info.row.original.status !== "up" && "bg-primary/30") || (info.row.original.status !== "up" && STATUS_COLORS.paused) ||
(threshold === MeterState.Good && "bg-green-500") || (threshold === MeterState.Good && STATUS_COLORS.up) ||
(threshold === MeterState.Warn && "bg-yellow-500") || (threshold === MeterState.Warn && STATUS_COLORS.pending) ||
"bg-red-600" STATUS_COLORS.down
)} )}
style={{ style={{
transform: `scalex(${val / 100})`, transform: `scalex(${val / 100})`,
@@ -308,12 +327,7 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
} }
export function IndicatorDot({ system, className }: { system: SystemRecord; className?: ClassValue }) { export function IndicatorDot({ system, className }: { system: SystemRecord; className?: ClassValue }) {
className ||= { className ||= STATUS_COLORS[system.status as keyof typeof STATUS_COLORS] || ""
"bg-green-500": system.status === "up",
"bg-red-500": system.status === "down",
"bg-primary/40": system.status === "paused",
"bg-yellow-500": system.status === "pending",
}
return ( return (
<span <span
className={cn("flex-shrink-0 size-2 rounded-full", className)} className={cn("flex-shrink-0 size-2 rounded-full", className)}

View File

@@ -68,7 +68,7 @@ export default function SystemsTable() {
} }
}, [filter]) }, [filter])
const columnDefs = useMemo(() => SystemsTableColumns(viewMode), []) const columnDefs = useMemo(() => SystemsTableColumns(viewMode), [viewMode])
const table = useReactTable({ const table = useReactTable({
data, data,

View File

@@ -2,6 +2,7 @@ import PocketBase from "pocketbase"
import { atom, map, PreinitializedWritableAtom } from "nanostores" import { atom, map, PreinitializedWritableAtom } from "nanostores"
import { AlertRecord, ChartTimes, SystemRecord, UserSettings } from "@/types" import { AlertRecord, ChartTimes, SystemRecord, UserSettings } from "@/types"
import { basePath } from "@/components/router" import { basePath } from "@/components/router"
import { Unit } from "./enums"
/** PocketBase JS Client */ /** PocketBase JS Client */
export const pb = new PocketBase(basePath) export const pb = new PocketBase(basePath)
@@ -39,10 +40,11 @@ export const $maxValues = atom(false)
export const $userSettings = map<UserSettings>({ export const $userSettings = map<UserSettings>({
chartTime: "1h", chartTime: "1h",
emails: [pb.authStore.record?.email || ""], emails: [pb.authStore.record?.email || ""],
unitNet: Unit.Bytes,
unitTemp: Unit.Celsius,
}) })
// update local storage on change // update local storage on change
$userSettings.subscribe((value) => { $userSettings.subscribe((value) => {
// console.log('user settings changed', value)
$chartTime.set(value.chartTime) $chartTime.set(value.chartTime)
}) })