improve container filtering performance

- also a few instances of autoformat
This commit is contained in:
henrygd
2025-09-09 16:59:16 -04:00
parent b2b54db409
commit 956880aa59
2 changed files with 37 additions and 18 deletions

View File

@@ -1,11 +1,11 @@
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts" import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart" import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { memo, useMemo } from "react" import { memo, useMemo } from "react"
import { cn, formatShortDate, chartMargin, toFixedFloat, formatBytes, decimalString } from "@/lib/utils" import { cn, formatShortDate, chartMargin, toFixedFloat, formatBytes, decimalString } from "@/lib/utils"
// import Spinner from '../spinner' // import Spinner from '../spinner'
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import { $containerFilter, $userSettings } from "@/lib/stores" import { $containerFilter, $userSettings } from "@/lib/stores"
import { ChartData } from "@/types" import type { ChartData } from "@/types"
import { Separator } from "../ui/separator" import { Separator } from "../ui/separator"
import { ChartType, Unit } from "@/lib/enums" import { ChartType, Unit } from "@/lib/enums"
import { useYAxisWidth } from "./hooks" import { useYAxisWidth } from "./hooks"
@@ -31,6 +31,7 @@ export default memo(function ContainerChart({
const isNetChart = chartType === ChartType.Network const isNetChart = chartType === ChartType.Network
// biome-ignore lint/correctness/useExhaustiveDependencies: not necessary
const { toolTipFormatter, dataFunction, tickFormatter } = useMemo(() => { const { toolTipFormatter, dataFunction, tickFormatter } = useMemo(() => {
const obj = {} as { const obj = {} as {
toolTipFormatter: (item: any, key: string) => React.ReactNode | string toolTipFormatter: (item: any, key: string) => React.ReactNode | string
@@ -47,7 +48,7 @@ export default memo(function ContainerChart({
const chartUnit = isNetChart ? userSettings.unitNet : Unit.Bytes const chartUnit = isNetChart ? userSettings.unitNet : Unit.Bytes
obj.tickFormatter = (val) => { obj.tickFormatter = (val) => {
const { value, unit } = formatBytes(val, isNetChart, chartUnit, true) const { value, unit } = formatBytes(val, isNetChart, chartUnit, true)
return updateYAxisWidth(toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit) return updateYAxisWidth(`${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`)
} }
} }
// tooltip formatter // tooltip formatter
@@ -74,10 +75,10 @@ export default memo(function ContainerChart({
} else if (chartType === ChartType.Memory) { } else if (chartType === ChartType.Memory) {
obj.toolTipFormatter = (item: any) => { obj.toolTipFormatter = (item: any) => {
const { value, unit } = formatBytes(item.value, false, Unit.Bytes, true) const { value, unit } = formatBytes(item.value, false, Unit.Bytes, true)
return decimalString(value) + " " + unit return `${decimalString(value)} ${unit}`
} }
} else { } else {
obj.toolTipFormatter = (item: any) => decimalString(item.value) + unit obj.toolTipFormatter = (item: any) => `${decimalString(item.value)} ${unit}`
} }
// data function // data function
if (isNetChart) { if (isNetChart) {
@@ -133,7 +134,7 @@ export default memo(function ContainerChart({
animationDuration={150} animationDuration={150}
truncate={true} truncate={true}
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
// @ts-ignore // @ts-expect-error
itemSorter={(a, b) => b.value - a.value} itemSorter={(a, b) => b.value - a.value}
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />} content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />}
/> />

View File

@@ -32,6 +32,7 @@ import { useIntersectionObserver } from "@/lib/use-intersection-observer"
import { import {
chartTimeData, chartTimeData,
cn, cn,
debounce,
decimalString, decimalString,
formatBytes, formatBytes,
getHostDisplayValue, getHostDisplayValue,
@@ -61,7 +62,6 @@ type ChartTimeData = {
chartTime: ChartTimes chartTime: ChartTimes
} }
const cache = new Map<string, ChartTimeData | SystemStatsRecord[] | ContainerStatsRecord[]>() const cache = new Map<string, ChartTimeData | SystemStatsRecord[] | ContainerStatsRecord[]>()
// create ticks and domain for charts // create ticks and domain for charts
@@ -88,7 +88,7 @@ function getTimeData(chartTime: ChartTimes, lastCreated: number) {
function addEmptyValues<T extends SystemStatsRecord | ContainerStatsRecord>( function addEmptyValues<T extends SystemStatsRecord | ContainerStatsRecord>(
prevRecords: T[], prevRecords: T[],
newRecords: T[], newRecords: T[],
expectedInterval: number, expectedInterval: number
) { ) {
const modifiedRecords: T[] = [] const modifiedRecords: T[] = []
let prevTime = (prevRecords.at(-1)?.created ?? 0) as number let prevTime = (prevRecords.at(-1)?.created ?? 0) as number
@@ -109,7 +109,11 @@ function addEmptyValues<T extends SystemStatsRecord | ContainerStatsRecord>(
return modifiedRecords return modifiedRecords
} }
async function getStats<T extends SystemStatsRecord | ContainerStatsRecord>(collection: string, system: SystemRecord, chartTime: ChartTimes): Promise<T[]> { async function getStats<T extends SystemStatsRecord | ContainerStatsRecord>(
collection: string,
system: SystemRecord,
chartTime: ChartTimes
): Promise<T[]> {
const cachedStats = cache.get(`${system.id}_${chartTime}_${collection}`) as T[] | undefined const cachedStats = cache.get(`${system.id}_${chartTime}_${collection}`) as T[] | undefined
const lastCached = cachedStats?.at(-1)?.created as number const lastCached = cachedStats?.at(-1)?.created as number
return await pb.collection<T>(collection).getFullList({ return await pb.collection<T>(collection).getFullList({
@@ -175,7 +179,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
const chartData: ChartData = useMemo(() => { const chartData: ChartData = useMemo(() => {
const lastCreated = Math.max( const lastCreated = Math.max(
(systemStats.at(-1)?.created as number) ?? 0, (systemStats.at(-1)?.created as number) ?? 0,
(containerData.at(-1)?.created as number) ?? 0, (containerData.at(-1)?.created as number) ?? 0
) )
return { return {
systemStats, systemStats,
@@ -212,7 +216,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
// get stats // get stats
// biome-ignore lint/correctness/useExhaustiveDependencies: not necessary // biome-ignore lint/correctness/useExhaustiveDependencies: not necessary
useEffect(() => { useEffect(() => {
if (!system.id || !chartTime) { if (!system.id || !chartTime) {
return return
} }
@@ -256,7 +260,6 @@ export default memo(function SystemDetail({ name }: { name: string }) {
}) })
}, [system, chartTime]) }, [system, chartTime])
// values for system info bar // values for system info bar
const systemInfo = useMemo(() => { const systemInfo = useMemo(() => {
if (!system.info) { if (!system.info) {
@@ -396,7 +399,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
return ( return (
<> <>
<div ref={chartWrapRef} className="grid gap-4 mb-14 overflow-x-clip"> <div ref={chartWrapRef} className="grid gap-4 mb-14 overflow-x-clip">
{/* system info */} {/* system info */}
<Card> <Card>
<div className="grid xl:flex gap-4 px-4 sm:px-6 pt-3 sm:pt-4 pb-5"> <div className="grid xl:flex gap-4 px-4 sm:px-6 pt-3 sm:pt-4 pb-5">
@@ -859,14 +862,24 @@ export default memo(function SystemDetail({ name }: { name: string }) {
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) { function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
const containerFilter = useStore(store) const containerFilter = useStore(store)
const { t } = useLingui() const { t } = useLingui()
const inputRef = useRef<HTMLInputElement>(null)
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { const debouncedStoreSet = useMemo(() => debounce((value: string) => store.set(value), 150), [store])
store.set(e.target.value)
}, [store]) const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
if (inputRef.current) {
inputRef.current.value = value
}
debouncedStoreSet(value)
},
[debouncedStoreSet]
)
return ( return (
<> <>
<Input placeholder={t`Filter...`} className="ps-4 pe-8" value={containerFilter} onChange={handleChange} /> <Input placeholder={t`Filter...`} className="ps-4 pe-8" onChange={handleChange} ref={inputRef} />
{containerFilter && ( {containerFilter && (
<Button <Button
type="button" type="button"
@@ -874,7 +887,12 @@ function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilt
size="icon" size="icon"
aria-label="Clear" aria-label="Clear"
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100" className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
onClick={() => store.set("")} onClick={() => {
if (inputRef.current) {
inputRef.current.value = ""
}
store.set("")
}}
> >
<XIcon className="h-4 w-4" /> <XIcon className="h-4 w-4" />
</Button> </Button>