mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 01:39:34 +08:00
refactor: js performance improvements
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,3 +19,4 @@ beszel/site/src/locales/**/*.ts
|
||||
__debug_*
|
||||
beszel/internal/agent/lhm/obj
|
||||
beszel/internal/agent/lhm/bin
|
||||
dockerfile_agent_dev
|
||||
|
@@ -29,41 +29,36 @@ export default memo(function ContainerChart({
|
||||
const isNetChart = chartType === ChartType.Network
|
||||
|
||||
const chartConfig = useMemo(() => {
|
||||
let config = {} as Record<
|
||||
string,
|
||||
{
|
||||
label: string
|
||||
color: string
|
||||
}
|
||||
>
|
||||
const totalUsage = {} as Record<string, number>
|
||||
for (let stats of containerData) {
|
||||
for (let key in stats) {
|
||||
if (!key || key === "created") {
|
||||
continue
|
||||
}
|
||||
if (!(key in totalUsage)) {
|
||||
totalUsage[key] = 0
|
||||
}
|
||||
if (isNetChart) {
|
||||
totalUsage[key] += (stats[key]?.nr ?? 0) + (stats[key]?.ns ?? 0)
|
||||
} else {
|
||||
// @ts-ignore
|
||||
totalUsage[key] += stats[key]?.[dataKey] ?? 0
|
||||
const config = {} as Record<string, { label: string; color: string }>
|
||||
const totalUsage = new Map<string, number>()
|
||||
|
||||
// calculate total usage of each container
|
||||
for (const stats of containerData) {
|
||||
for (const key in stats) {
|
||||
if (!key || key === "created") continue
|
||||
|
||||
const currentTotal = totalUsage.get(key) ?? 0
|
||||
const increment = isNetChart
|
||||
? (stats[key]?.nr ?? 0) + (stats[key]?.ns ?? 0)
|
||||
: // @ts-ignore
|
||||
stats[key]?.[dataKey] ?? 0
|
||||
|
||||
totalUsage.set(key, currentTotal + increment)
|
||||
}
|
||||
}
|
||||
}
|
||||
let keys = Object.keys(totalUsage)
|
||||
keys.sort((a, b) => (totalUsage[a] > totalUsage[b] ? -1 : 1))
|
||||
const length = keys.length
|
||||
for (let i = 0; i < length; i++) {
|
||||
const key = keys[i]
|
||||
|
||||
// Sort keys and generate colors based on usage
|
||||
const sortedEntries = Array.from(totalUsage.entries()).sort(([, a], [, b]) => b - a)
|
||||
|
||||
const length = sortedEntries.length
|
||||
sortedEntries.forEach(([key], i) => {
|
||||
const hue = ((i * 360) / length) % 360
|
||||
config[key] = {
|
||||
label: key,
|
||||
color: `hsl(${hue}, 60%, 55%)`,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return config satisfies ChartConfig
|
||||
}, [chartData])
|
||||
|
||||
@@ -124,6 +119,8 @@ export default memo(function ContainerChart({
|
||||
return obj
|
||||
}, [])
|
||||
|
||||
const filterLower = filter?.toLowerCase()
|
||||
|
||||
// console.log('rendered at', new Date())
|
||||
|
||||
if (containerData.length === 0) {
|
||||
@@ -165,7 +162,7 @@ export default memo(function ContainerChart({
|
||||
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />}
|
||||
/>
|
||||
{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 strokeOpacity = filtered ? 0.1 : 1
|
||||
return (
|
||||
|
@@ -56,16 +56,18 @@ import { buttonVariants } from "../ui/button"
|
||||
import { t } from "@lingui/core/macro"
|
||||
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"
|
||||
* @returns - Column definitions for the systems table
|
||||
*/
|
||||
export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] {
|
||||
const statusTranslations = {
|
||||
up: () => t`Up`.toLowerCase(),
|
||||
down: () => t`Down`.toLowerCase(),
|
||||
paused: () => t`Paused`.toLowerCase(),
|
||||
}
|
||||
return [
|
||||
{
|
||||
size: 200,
|
||||
@@ -73,18 +75,35 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
||||
accessorKey: "name",
|
||||
id: "system",
|
||||
name: () => t`System`,
|
||||
filterFn: (row, _, filterVal) => {
|
||||
const filterLower = filterVal.toLowerCase()
|
||||
filterFn: (() => {
|
||||
let filterInput = ""
|
||||
let filterInputLower = ""
|
||||
const nameCache = new Map<string, string>()
|
||||
const statusTranslations = {
|
||||
up: t`Up`.toLowerCase(),
|
||||
down: t`Down`.toLowerCase(),
|
||||
paused: t`Paused`.toLowerCase(),
|
||||
} as const
|
||||
|
||||
// match filter value against name or translated status
|
||||
return (row, _, newFilterInput) => {
|
||||
const { name, status } = row.original
|
||||
// Check if the filter matches the name or status for this row
|
||||
if (
|
||||
name.toLowerCase().includes(filterLower) ||
|
||||
statusTranslations[status as keyof typeof statusTranslations]?.().includes(filterLower)
|
||||
) {
|
||||
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
|
||||
}
|
||||
return false
|
||||
},
|
||||
const statusLower = statusTranslations[status as keyof typeof statusTranslations]
|
||||
return statusLower?.includes(filterInputLower) || false
|
||||
}
|
||||
})(),
|
||||
enableHiding: false,
|
||||
invertSorting: false,
|
||||
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">
|
||||
<span
|
||||
className={cn("inline-block size-2 rounded-full me-0.5", {
|
||||
"bg-green-500": threshold === MeterState.Good,
|
||||
"bg-yellow-500": threshold === MeterState.Warn,
|
||||
"bg-red-600": threshold === MeterState.Crit,
|
||||
[STATUS_COLORS.up]: threshold === MeterState.Good,
|
||||
[STATUS_COLORS.pending]: threshold === MeterState.Warn,
|
||||
[STATUS_COLORS.down]: threshold === MeterState.Crit,
|
||||
})}
|
||||
/>
|
||||
{loadAverages?.map((la, i) => (
|
||||
@@ -190,7 +209,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
||||
if (sys.status === "paused") {
|
||||
return null
|
||||
}
|
||||
const userSettings = useStore($userSettings)
|
||||
const userSettings = useStore($userSettings, { keys: ["unitNet"] })
|
||||
const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false)
|
||||
return (
|
||||
<span className="tabular-nums whitespace-nowrap">
|
||||
@@ -212,7 +231,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
||||
if (!val) {
|
||||
return null
|
||||
}
|
||||
const userSettings = useStore($userSettings)
|
||||
const userSettings = useStore($userSettings, { keys: ["unitTemp"] })
|
||||
const { value, unit } = formatTemperature(val, userSettings.unitTemp)
|
||||
return (
|
||||
<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
|
||||
system={system}
|
||||
className={
|
||||
(system.status !== "up" && "bg-primary/30") ||
|
||||
(version === globalThis.BESZEL.HUB_VERSION && "bg-green-500") ||
|
||||
"bg-yellow-500"
|
||||
(system.status !== "up" && STATUS_COLORS.paused) ||
|
||||
(version === globalThis.BESZEL.HUB_VERSION && STATUS_COLORS.up) ||
|
||||
STATUS_COLORS.pending
|
||||
}
|
||||
/>
|
||||
<span className="truncate max-w-14">{info.getValue() as string}</span>
|
||||
@@ -293,10 +312,10 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
|
||||
<span
|
||||
className={cn(
|
||||
"absolute inset-0 w-full h-full origin-left",
|
||||
(info.row.original.status !== "up" && "bg-primary/30") ||
|
||||
(threshold === MeterState.Good && "bg-green-500") ||
|
||||
(threshold === MeterState.Warn && "bg-yellow-500") ||
|
||||
"bg-red-600"
|
||||
(info.row.original.status !== "up" && STATUS_COLORS.paused) ||
|
||||
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
||||
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
||||
STATUS_COLORS.down
|
||||
)}
|
||||
style={{
|
||||
transform: `scalex(${val / 100})`,
|
||||
@@ -308,12 +327,7 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
|
||||
}
|
||||
|
||||
export function IndicatorDot({ system, className }: { system: SystemRecord; className?: ClassValue }) {
|
||||
className ||= {
|
||||
"bg-green-500": system.status === "up",
|
||||
"bg-red-500": system.status === "down",
|
||||
"bg-primary/40": system.status === "paused",
|
||||
"bg-yellow-500": system.status === "pending",
|
||||
}
|
||||
className ||= STATUS_COLORS[system.status as keyof typeof STATUS_COLORS] || ""
|
||||
return (
|
||||
<span
|
||||
className={cn("flex-shrink-0 size-2 rounded-full", className)}
|
||||
|
@@ -68,7 +68,7 @@ export default function SystemsTable() {
|
||||
}
|
||||
}, [filter])
|
||||
|
||||
const columnDefs = useMemo(() => SystemsTableColumns(viewMode), [])
|
||||
const columnDefs = useMemo(() => SystemsTableColumns(viewMode), [viewMode])
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
|
@@ -2,6 +2,7 @@ import PocketBase from "pocketbase"
|
||||
import { atom, map, PreinitializedWritableAtom } from "nanostores"
|
||||
import { AlertRecord, ChartTimes, SystemRecord, UserSettings } from "@/types"
|
||||
import { basePath } from "@/components/router"
|
||||
import { Unit } from "./enums"
|
||||
|
||||
/** PocketBase JS Client */
|
||||
export const pb = new PocketBase(basePath)
|
||||
@@ -39,10 +40,11 @@ export const $maxValues = atom(false)
|
||||
export const $userSettings = map<UserSettings>({
|
||||
chartTime: "1h",
|
||||
emails: [pb.authStore.record?.email || ""],
|
||||
unitNet: Unit.Bytes,
|
||||
unitTemp: Unit.Celsius,
|
||||
})
|
||||
// update local storage on change
|
||||
$userSettings.subscribe((value) => {
|
||||
// console.log('user settings changed', value)
|
||||
$chartTime.set(value.chartTime)
|
||||
})
|
||||
|
||||
|
Reference in New Issue
Block a user