2024-04-04 05:22:11 +02:00
|
|
|
import useSWR from "swr";
|
|
|
|
import { FrigateStats } from "@/types/stats";
|
2024-04-12 14:31:30 +02:00
|
|
|
import { useEffect, useState } from "react";
|
2024-04-04 05:22:11 +02:00
|
|
|
import TimeAgo from "@/components/dynamic/TimeAgo";
|
|
|
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
2024-04-04 18:24:23 +02:00
|
|
|
import { isDesktop, isMobile } from "react-device-detect";
|
|
|
|
import GeneralMetrics from "@/views/system/GeneralMetrics";
|
|
|
|
import StorageMetrics from "@/views/system/StorageMetrics";
|
|
|
|
import { LuActivity, LuHardDrive } from "react-icons/lu";
|
|
|
|
import { FaVideo } from "react-icons/fa";
|
|
|
|
import Logo from "@/components/Logo";
|
|
|
|
import useOptimisticState from "@/hooks/use-optimistic-state";
|
2024-04-04 22:55:04 +02:00
|
|
|
import CameraMetrics from "@/views/system/CameraMetrics";
|
2024-04-29 00:59:03 +02:00
|
|
|
import { useHashState } from "@/hooks/use-overlay-state";
|
|
|
|
import { capitalizeFirstLetter } from "@/utils/stringUtil";
|
2024-10-31 20:48:26 +01:00
|
|
|
import { Toaster } from "@/components/ui/sonner";
|
2024-04-04 05:22:11 +02:00
|
|
|
|
|
|
|
const metrics = ["general", "storage", "cameras"] as const;
|
|
|
|
type SystemMetric = (typeof metrics)[number];
|
2023-12-08 14:33:22 +01:00
|
|
|
|
|
|
|
function System() {
|
2024-04-04 05:22:11 +02:00
|
|
|
// stats page
|
|
|
|
|
2024-04-29 00:59:03 +02:00
|
|
|
const [page, setPage] = useHashState<SystemMetric>();
|
|
|
|
const [pageToggle, setPageToggle] = useOptimisticState(
|
|
|
|
page ?? "general",
|
|
|
|
setPage,
|
|
|
|
100,
|
|
|
|
);
|
2024-04-04 05:22:11 +02:00
|
|
|
const [lastUpdated, setLastUpdated] = useState<number>(Date.now() / 1000);
|
|
|
|
|
2024-04-12 14:31:30 +02:00
|
|
|
useEffect(() => {
|
2024-04-29 00:59:03 +02:00
|
|
|
if (pageToggle) {
|
|
|
|
document.title = `${capitalizeFirstLetter(pageToggle)} Stats - Frigate`;
|
|
|
|
}
|
2024-04-12 14:31:30 +02:00
|
|
|
}, [pageToggle]);
|
|
|
|
|
2024-04-04 05:22:11 +02:00
|
|
|
// stats collection
|
|
|
|
|
|
|
|
const { data: statsSnapshot } = useSWR<FrigateStats>("stats", {
|
|
|
|
revalidateOnFocus: false,
|
|
|
|
});
|
|
|
|
|
2023-12-08 14:33:22 +01:00
|
|
|
return (
|
2024-05-14 17:06:44 +02:00
|
|
|
<div className="flex size-full flex-col p-2">
|
2024-10-31 20:48:26 +01:00
|
|
|
<Toaster position="top-center" />
|
2024-05-14 17:06:44 +02:00
|
|
|
<div className="relative flex h-11 w-full items-center justify-between">
|
2024-04-04 18:24:23 +02:00
|
|
|
{isMobile && (
|
2024-05-14 17:06:44 +02:00
|
|
|
<Logo className="absolute inset-x-1/2 h-8 -translate-x-1/2" />
|
2024-04-04 18:24:23 +02:00
|
|
|
)}
|
2024-04-04 05:22:11 +02:00
|
|
|
<ToggleGroup
|
2024-05-14 17:06:44 +02:00
|
|
|
className="*:rounded-md *:px-3 *:py-4"
|
2024-04-04 05:22:11 +02:00
|
|
|
type="single"
|
|
|
|
size="sm"
|
2024-04-04 18:24:23 +02:00
|
|
|
value={pageToggle}
|
2024-04-04 05:22:11 +02:00
|
|
|
onValueChange={(value: SystemMetric) => {
|
|
|
|
if (value) {
|
2024-04-04 18:24:23 +02:00
|
|
|
setPageToggle(value);
|
2024-04-04 05:22:11 +02:00
|
|
|
}
|
|
|
|
}} // don't allow the severity to be unselected
|
|
|
|
>
|
|
|
|
{Object.values(metrics).map((item) => (
|
|
|
|
<ToggleGroupItem
|
|
|
|
key={item}
|
2024-04-19 19:17:23 +02:00
|
|
|
className={`flex items-center justify-between gap-2 ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
|
2024-04-04 05:22:11 +02:00
|
|
|
value={item}
|
|
|
|
aria-label={`Select ${item}`}
|
|
|
|
>
|
2024-04-04 18:24:23 +02:00
|
|
|
{item == "general" && <LuActivity className="size-4" />}
|
|
|
|
{item == "storage" && <LuHardDrive className="size-4" />}
|
|
|
|
{item == "cameras" && <FaVideo className="size-4" />}
|
|
|
|
{isDesktop && <div className="capitalize">{item}</div>}
|
2024-04-04 05:22:11 +02:00
|
|
|
</ToggleGroupItem>
|
|
|
|
))}
|
|
|
|
</ToggleGroup>
|
|
|
|
|
2024-05-14 17:06:44 +02:00
|
|
|
<div className="flex h-full items-center">
|
2024-04-04 05:22:11 +02:00
|
|
|
{lastUpdated && (
|
2024-05-14 17:06:44 +02:00
|
|
|
<div className="h-full content-center text-sm text-muted-foreground">
|
2024-04-04 05:22:11 +02:00
|
|
|
Last refreshed: <TimeAgo time={lastUpdated * 1000} dense />
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="mt-2 flex items-end gap-2">
|
2024-05-14 17:06:44 +02:00
|
|
|
<div className="h-full content-center font-medium">System</div>
|
2024-04-04 05:22:11 +02:00
|
|
|
{statsSnapshot && (
|
2024-05-14 17:06:44 +02:00
|
|
|
<div className="h-full content-center text-sm text-muted-foreground">
|
2024-04-04 05:22:11 +02:00
|
|
|
{statsSnapshot.service.version}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
{page == "general" && (
|
|
|
|
<GeneralMetrics
|
|
|
|
lastUpdated={lastUpdated}
|
|
|
|
setLastUpdated={setLastUpdated}
|
|
|
|
/>
|
|
|
|
)}
|
2024-04-04 18:24:23 +02:00
|
|
|
{page == "storage" && <StorageMetrics setLastUpdated={setLastUpdated} />}
|
2024-04-04 22:55:04 +02:00
|
|
|
{page == "cameras" && (
|
|
|
|
<CameraMetrics
|
|
|
|
lastUpdated={lastUpdated}
|
|
|
|
setLastUpdated={setLastUpdated}
|
|
|
|
/>
|
|
|
|
)}
|
2024-04-04 05:22:11 +02:00
|
|
|
</div>
|
2023-12-08 14:33:22 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default System;
|