diff --git a/frigate/stats/emitter.py b/frigate/stats/emitter.py
index 2c29548e9..a90be5271 100644
--- a/frigate/stats/emitter.py
+++ b/frigate/stats/emitter.py
@@ -16,7 +16,8 @@ from frigate.types import StatsTrackingTypes
logger = logging.getLogger(__name__)
-MAX_STATS_POINTS = 120
+MAX_STATS_POINTS = 80
+FREQUENCY_STATS_POINTS = 15
class StatsEmitter(threading.Thread):
@@ -70,9 +71,9 @@ class StatsEmitter(threading.Thread):
def run(self) -> None:
time.sleep(10)
for counter in itertools.cycle(
- range(int(self.config.mqtt.stats_interval / 10))
+ range(int(self.config.mqtt.stats_interval / FREQUENCY_STATS_POINTS))
):
- if self.stop_event.wait(10):
+ if self.stop_event.wait(FREQUENCY_STATS_POINTS):
break
logger.debug("Starting stats collection")
diff --git a/web/src/App.tsx b/web/src/App.tsx
index 98385fc20..2f0853200 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -11,7 +11,6 @@ import { Suspense, lazy } from "react";
const Live = lazy(() => import("@/pages/Live"));
const Events = lazy(() => import("@/pages/Events"));
const Export = lazy(() => import("@/pages/Export"));
-const Storage = lazy(() => import("@/pages/Storage"));
const SubmitPlus = lazy(() => import("@/pages/SubmitPlus"));
const ConfigEditor = lazy(() => import("@/pages/ConfigEditor"));
const System = lazy(() => import("@/pages/System"));
@@ -38,7 +37,6 @@ function App() {
} />
} />
} />
- } />
} />
} />
} />
diff --git a/web/src/components/filter/ReviewFilterGroup.tsx b/web/src/components/filter/ReviewFilterGroup.tsx
index 4d2e1db8e..f5b09836b 100644
--- a/web/src/components/filter/ReviewFilterGroup.tsx
+++ b/web/src/components/filter/ReviewFilterGroup.tsx
@@ -2,7 +2,7 @@ import { Button } from "../ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import useSWR from "swr";
import { CameraGroupConfig, FrigateConfig } from "@/types/frigateConfig";
-import { useCallback, useEffect, useMemo, useState } from "react";
+import { useCallback, useMemo, useState } from "react";
import {
DropdownMenu,
DropdownMenuContent,
@@ -29,6 +29,7 @@ import ReviewActivityCalendar from "../overlay/ReviewActivityCalendar";
import MobileReviewSettingsDrawer, {
DrawerFeatures,
} from "../overlay/MobileReviewSettingsDrawer";
+import useOptimisticState from "@/hooks/use-optimistic-state";
const REVIEW_FILTERS = [
"cameras",
@@ -361,13 +362,19 @@ function ShowReviewFilter({
showReviewed,
setShowReviewed,
}: ShowReviewedFilterProps) {
+ const [showReviewedSwitch, setShowReviewedSwitch] = useOptimisticState(
+ showReviewed,
+ setShowReviewed,
+ );
return (
<>
setShowReviewed(showReviewed == 0 ? 1 : 0)}
+ checked={showReviewedSwitch == 1}
+ onCheckedChange={() =>
+ setShowReviewedSwitch(showReviewedSwitch == 0 ? 1 : 0)
+ }
/>
diff --git a/web/src/components/graph/SystemGraph.tsx b/web/src/components/graph/SystemGraph.tsx
index ec750dceb..a6ca219b0 100644
--- a/web/src/components/graph/SystemGraph.tsx
+++ b/web/src/components/graph/SystemGraph.tsx
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useMemo } from "react";
import Chart from "react-apexcharts";
import useSWR from "swr";
-type SystemGraphProps = {
+type ThresholdBarGraphProps = {
graphId: string;
name: string;
unit: string;
@@ -13,14 +13,14 @@ type SystemGraphProps = {
updateTimes: number[];
data: ApexAxisChartSeries;
};
-export default function SystemGraph({
+export function ThresholdBarGraph({
graphId,
name,
unit,
threshold,
updateTimes,
data,
-}: SystemGraphProps) {
+}: ThresholdBarGraphProps) {
const { data: config } = useSWR("config", {
revalidateOnFocus: false,
});
@@ -87,8 +87,12 @@ export default function SystemGraph({
tooltip: {
theme: systemTheme || theme,
},
+ markers: {
+ size: 0,
+ },
xaxis: {
- tickAmount: 6,
+ tickAmount: 4,
+ tickPlacement: "on",
labels: {
formatter: formatTime,
},
@@ -104,7 +108,7 @@ export default function SystemGraph({
min: 0,
max: threshold.warning + 10,
},
- };
+ } as ApexCharts.ApexOptions;
}, [graphId, threshold, systemTheme, theme, formatTime]);
useEffect(() => {
@@ -124,3 +128,110 @@ export default function SystemGraph({
);
}
+
+const getUnitSize = (MB: number) => {
+ if (isNaN(MB) || MB < 0) return "Invalid number";
+ if (MB < 1024) return `${MB} MiB`;
+ if (MB < 1048576) return `${(MB / 1024).toFixed(2)} GiB`;
+
+ return `${(MB / 1048576).toFixed(2)} TiB`;
+};
+
+type StorageGraphProps = {
+ graphId: string;
+ used: number;
+ total: number;
+};
+export function StorageGraph({ graphId, used, total }: StorageGraphProps) {
+ const { theme, systemTheme } = useTheme();
+
+ const options = useMemo(() => {
+ return {
+ chart: {
+ id: graphId,
+ background: (systemTheme || theme) == "dark" ? "#404040" : "#E5E5E5",
+ selection: {
+ enabled: false,
+ },
+ toolbar: {
+ show: false,
+ },
+ zoom: {
+ enabled: false,
+ },
+ },
+ grid: {
+ show: false,
+ padding: {
+ bottom: -40,
+ top: -60,
+ left: -20,
+ right: 0,
+ },
+ },
+ legend: {
+ show: false,
+ },
+ dataLabels: {
+ enabled: false,
+ },
+ plotOptions: {
+ bar: {
+ horizontal: true,
+ },
+ },
+ tooltip: {
+ theme: systemTheme || theme,
+ },
+ xaxis: {
+ axisBorder: {
+ show: false,
+ },
+ axisTicks: {
+ show: false,
+ },
+ labels: {
+ show: false,
+ },
+ },
+ yaxis: {
+ show: false,
+ min: 0,
+ max: 100,
+ },
+ };
+ }, [graphId, systemTheme, theme]);
+
+ useEffect(() => {
+ ApexCharts.exec(graphId, "updateOptions", options, true, true);
+ }, [graphId, options]);
+
+ return (
+
+
+
+
+ {getUnitSize(used)}
+
+
/
+
+ {getUnitSize(total)}
+
+
+
+ {Math.round((used / total) * 100)}%
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/components/overlay/ExportDialog.tsx b/web/src/components/overlay/ExportDialog.tsx
index a9d597f83..5475dd00c 100644
--- a/web/src/components/overlay/ExportDialog.tsx
+++ b/web/src/components/overlay/ExportDialog.tsx
@@ -216,11 +216,11 @@ export function ExportContent({
Export
-
+
>
)}
onSelectTime(value as ExportOption)}
>
{EXPORT_OPTIONS.map((opt) => {
@@ -254,13 +254,13 @@ export function ExportContent({
/>
)}
setName(e.target.value)}
/>
- {isDesktop && }
+ {isDesktop && }
@@ -371,7 +371,7 @@ function CustomTimeSelector({
return (
{
const cameraConfig = config.cameras[camera];
cameraConfig.objects.track.forEach((label) => {
- if (!ATTRIBUTES.includes(label)) {
- labels.add(label);
- }
+ labels.add(label);
});
if (cameraConfig.audio.enabled_in_config) {
diff --git a/web/src/components/settings/GeneralSettings.tsx b/web/src/components/settings/GeneralSettings.tsx
index e225257f3..b13051ea4 100644
--- a/web/src/components/settings/GeneralSettings.tsx
+++ b/web/src/components/settings/GeneralSettings.tsx
@@ -1,7 +1,6 @@
import {
LuActivity,
LuGithub,
- LuHardDrive,
LuLifeBuoy,
LuList,
LuMoon,
@@ -138,18 +137,6 @@ export default function GeneralSettings({ className }: GeneralSettings) {
System
-
-
-