diff --git a/web/src/components/graph/SystemGraph.tsx b/web/src/components/graph/SystemGraph.tsx index 5fafb6a7d..36f90ce71 100644 --- a/web/src/components/graph/SystemGraph.tsx +++ b/web/src/components/graph/SystemGraph.tsx @@ -125,6 +125,108 @@ export function ThresholdBarGraph({ ); } -export function StorageGraph() { - +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; + data: ApexAxisChartSeries; +}; +export function StorageGraph({ + graphId, + used, + total, + data, +}: 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/pages/System.tsx b/web/src/pages/System.tsx index 5fe6a7e16..af81543be 100644 --- a/web/src/pages/System.tsx +++ b/web/src/pages/System.tsx @@ -5,6 +5,7 @@ import TimeAgo from "@/components/dynamic/TimeAgo"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { isDesktop } from "react-device-detect"; import GeneralMetrics from "@/views/system/GeneralMetrics"; +import StorageMetrics from "@/views/system/StorageMetrics"; const metrics = ["general", "storage", "cameras"] as const; type SystemMetric = (typeof metrics)[number]; @@ -70,6 +71,12 @@ function System() { setLastUpdated={setLastUpdated} /> )} + {page == "storage" && ( + + )} ); } diff --git a/web/src/views/system/StorageMetrics.tsx b/web/src/views/system/StorageMetrics.tsx new file mode 100644 index 000000000..d428aba4f --- /dev/null +++ b/web/src/views/system/StorageMetrics.tsx @@ -0,0 +1,59 @@ +import { StorageGraph } from "@/components/graph/SystemGraph"; +import { useMemo } from "react"; +import useSWR from "swr"; + +type CameraStorage = { + [key: string]: { + bandwidth: number; + usage: number; + usage_percent: number; + }; +}; + +type StorageMetricsProps = { + lastUpdated: number; + setLastUpdated: (last: number) => void; +}; +export default function StorageMetrics({ + lastUpdated, + setLastUpdated, +}: StorageMetricsProps) { + const { data: storage } = useSWR("recordings/storage"); + + const totalStorage = useMemo(() => { + if (!storage) { + return undefined; + } + + const totalStorage = { + total: 0, + }; + + Object.values(storage).forEach((cam) => (totalStorage.total += cam.usage)); + + return totalStorage; + }, [storage]); + + if (!totalStorage) { + return; + } + + return ( +
+
+ General Storage +
+
+
+
Recordings
+ +
+
+
+ ); +}