Improve metrics UI performance (#22691)

* embed cpu/mem stats into detectors, cameras, and processes

so history consumers don't need the full cpu_usages dict

* support dot-notation for nested keys

to avoid returning large objects when only specific subfields are needed

* fix setLastUpdated being called inside useMemo

this triggered a setState-during-render warning, so moved to a useEffect

* frontend types

* frontend

hide instead of unmount all graphs - re-rendering is much more expensive and disruptive than the amount of dom memory required

keep track of visited tabs to keep them mounted rather than re-mounting or mounting all tabs

add isActive prop to all charts to re-trigger animation when switching metrics tabs

fix chart data padding bug where the loop used number of series rather than number of data points

fix bug where only a shallow copy of the array was used for mutation

fix missing key prop causing console logs

* add isactive after rebase

* formatting

* skip None values in filtered output for dot notation
This commit is contained in:
Josh Hawkins
2026-03-29 11:58:47 -06:00
committed by GitHub
parent f002513d36
commit f44f485f48
10 changed files with 330 additions and 93 deletions
+41 -18
View File
@@ -1,6 +1,6 @@
import useSWR from "swr";
import { FrigateStats } from "@/types/stats";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import TimeAgo from "@/components/dynamic/TimeAgo";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import { isDesktop, isMobile } from "react-device-detect";
@@ -49,7 +49,17 @@ function System() {
setPage,
100,
);
const [lastUpdated, setLastUpdated] = useState<number>(Date.now() / 1000);
const [lastUpdated, setLastUpdated] = useState<number>(
Math.floor(Date.now() / 1000),
);
// Track which tabs have been visited so we can keep them mounted after first visit.
// Using a ref updated during render avoids extra render cycles from state/effects.
const visitedTabsRef = useRef(new Set<string>());
if (page) {
visitedTabsRef.current.add(page);
}
const visitedTabs = visitedTabsRef.current;
useEffect(() => {
if (pageToggle) {
@@ -116,24 +126,37 @@ function System() {
</div>
)}
</div>
{page == "general" && (
<GeneralMetrics
lastUpdated={lastUpdated}
setLastUpdated={setLastUpdated}
/>
{visitedTabs.has("general") && (
<div className={page == "general" ? "contents" : "hidden"}>
<GeneralMetrics
lastUpdated={lastUpdated}
setLastUpdated={setLastUpdated}
isActive={page == "general"}
/>
</div>
)}
{page == "enrichments" && (
<EnrichmentMetrics
lastUpdated={lastUpdated}
setLastUpdated={setLastUpdated}
/>
{metrics.includes("enrichments") && visitedTabs.has("enrichments") && (
<div className={page == "enrichments" ? "contents" : "hidden"}>
<EnrichmentMetrics
lastUpdated={lastUpdated}
setLastUpdated={setLastUpdated}
isActive={page == "enrichments"}
/>
</div>
)}
{page == "storage" && <StorageMetrics setLastUpdated={setLastUpdated} />}
{page == "cameras" && (
<CameraMetrics
lastUpdated={lastUpdated}
setLastUpdated={setLastUpdated}
/>
{visitedTabs.has("storage") && (
<div className={page == "storage" ? "contents" : "hidden"}>
<StorageMetrics setLastUpdated={setLastUpdated} />
</div>
)}
{visitedTabs.has("cameras") && (
<div className={page == "cameras" ? "contents" : "hidden"}>
<CameraMetrics
lastUpdated={lastUpdated}
setLastUpdated={setLastUpdated}
isActive={page == "cameras"}
/>
</div>
)}
</div>
);