diff --git a/.gitignore b/.gitignore index ba363d3..0c93d66 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ beszel/site/src/locales/**/*.ts __debug_* beszel/internal/agent/lhm/obj beszel/internal/agent/lhm/bin +dockerfile_agent_dev diff --git a/beszel/site/src/components/charts/container-chart.tsx b/beszel/site/src/components/charts/container-chart.tsx index 7d32566..fbd8497 100644 --- a/beszel/site/src/components/charts/container-chart.tsx +++ b/beszel/site/src/components/charts/container-chart.tsx @@ -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 - 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 + const totalUsage = new Map() + + // 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={} /> {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 ( diff --git a/beszel/site/src/components/systems-table/systems-table-columns.tsx b/beszel/site/src/components/systems-table/systems-table-columns.tsx index 28e0244..cb4a1b3 100644 --- a/beszel/site/src/components/systems-table/systems-table-columns.tsx +++ b/beszel/site/src/components/systems-table/systems-table-columns.tsx @@ -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[] { - 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() - 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) - ) { - return true + filterFn: (() => { + let filterInput = "" + let filterInputLower = "" + const nameCache = new Map() + 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 + 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, invertSorting: false, Icon: ServerIcon, @@ -166,9 +185,9 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
{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 ( @@ -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 ( @@ -241,9 +260,9 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD {info.getValue() as string} @@ -293,10 +312,10 @@ function TableCellWithMeter(info: CellContext) { ) { } 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 ( SystemsTableColumns(viewMode), []) + const columnDefs = useMemo(() => SystemsTableColumns(viewMode), [viewMode]) const table = useReactTable({ data, diff --git a/beszel/site/src/lib/stores.ts b/beszel/site/src/lib/stores.ts index e8167b0..02118d6 100644 --- a/beszel/site/src/lib/stores.ts +++ b/beszel/site/src/lib/stores.ts @@ -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({ 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) })