2023-12-14 04:15:10 +01:00
|
|
|
import { useWs } from "@/api/ws";
|
|
|
|
import ActivityIndicator from "@/components/ui/activity-indicator";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
2023-12-08 14:33:22 +01:00
|
|
|
import Heading from "@/components/ui/heading";
|
2023-12-14 04:15:10 +01:00
|
|
|
import {
|
|
|
|
Table,
|
|
|
|
TableBody,
|
|
|
|
TableCell,
|
|
|
|
TableHead,
|
|
|
|
TableHeader,
|
|
|
|
TableRow,
|
|
|
|
} from "@/components/ui/table";
|
|
|
|
import {
|
|
|
|
Tooltip,
|
|
|
|
TooltipContent,
|
|
|
|
TooltipProvider,
|
|
|
|
TooltipTrigger,
|
|
|
|
} from "@/components/ui/tooltip";
|
|
|
|
import { useMemo } from "react";
|
|
|
|
import { LuAlertCircle } from "react-icons/lu";
|
|
|
|
import useSWR from "swr";
|
|
|
|
|
|
|
|
type CameraStorage = {
|
|
|
|
[key: string]: {
|
|
|
|
bandwidth: number;
|
|
|
|
usage: number;
|
|
|
|
usage_percent: number;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const emptyObject = Object.freeze({});
|
2023-12-08 14:33:22 +01:00
|
|
|
|
|
|
|
function Storage() {
|
2023-12-14 04:15:10 +01:00
|
|
|
const { data: storage } = useSWR<CameraStorage>("recordings/storage");
|
|
|
|
|
|
|
|
const {
|
|
|
|
value: { payload: stats },
|
|
|
|
} = useWs("stats", "");
|
|
|
|
const { data: initialStats } = useSWR("stats");
|
|
|
|
|
|
|
|
const { service } = stats || initialStats || emptyObject;
|
|
|
|
|
|
|
|
const hasSeparateMedia = useMemo(() => {
|
|
|
|
return (
|
|
|
|
service &&
|
|
|
|
service["storage"]["/media/frigate/recordings"]["total"] !=
|
|
|
|
service["storage"]["/media/frigate/clips"]["total"]
|
|
|
|
);
|
2024-02-28 23:23:56 +01:00
|
|
|
}, [service]);
|
2023-12-14 04:15:10 +01:00
|
|
|
|
|
|
|
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`;
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!service || !storage) {
|
|
|
|
return <ActivityIndicator />;
|
|
|
|
}
|
|
|
|
|
2023-12-08 14:33:22 +01:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Heading as="h2">Storage</Heading>
|
2023-12-14 04:15:10 +01:00
|
|
|
|
|
|
|
<Heading className="my-4" as="h3">
|
|
|
|
Overview
|
|
|
|
</Heading>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
<Card>
|
|
|
|
<CardHeader>
|
2023-12-16 12:06:02 +01:00
|
|
|
<div className="flex items-center">
|
2023-12-14 04:15:10 +01:00
|
|
|
<CardTitle>Data</CardTitle>
|
|
|
|
<TooltipProvider>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button size="icon" variant="ghost">
|
|
|
|
<LuAlertCircle />
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>
|
|
|
|
<p>
|
|
|
|
Overview of total used storage and total capacity of the
|
|
|
|
drives that hold the recordings and snapshots directories.
|
|
|
|
</p>
|
|
|
|
</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
</TooltipProvider>
|
|
|
|
</div>
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
|
|
<Table>
|
|
|
|
<TableHeader>
|
|
|
|
<TableRow>
|
|
|
|
<TableHead>Location</TableHead>
|
|
|
|
<TableHead>Used</TableHead>
|
|
|
|
<TableHead>Total</TableHead>
|
|
|
|
</TableRow>
|
|
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
|
|
<TableRow>
|
|
|
|
<TableCell>
|
|
|
|
{hasSeparateMedia ? "Recordings" : "Recordings & Snapshots"}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{getUnitSize(
|
2024-02-28 23:23:56 +01:00
|
|
|
service["storage"]["/media/frigate/recordings"]["used"],
|
2023-12-14 04:15:10 +01:00
|
|
|
)}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{getUnitSize(
|
2024-02-28 23:23:56 +01:00
|
|
|
service["storage"]["/media/frigate/recordings"]["total"],
|
2023-12-14 04:15:10 +01:00
|
|
|
)}
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
{hasSeparateMedia && (
|
|
|
|
<TableRow>
|
|
|
|
<TableCell>Snapshots</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{getUnitSize(
|
2024-02-28 23:23:56 +01:00
|
|
|
service["storage"]["/media/frigate/clips"]["used"],
|
2023-12-14 04:15:10 +01:00
|
|
|
)}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{getUnitSize(
|
2024-02-28 23:23:56 +01:00
|
|
|
service["storage"]["/media/frigate/clips"]["total"],
|
2023-12-14 04:15:10 +01:00
|
|
|
)}
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
)}
|
|
|
|
</TableBody>
|
|
|
|
</Table>
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
<Card>
|
|
|
|
<CardHeader>
|
2023-12-16 12:06:02 +01:00
|
|
|
<div className="flex items-center">
|
2023-12-14 04:15:10 +01:00
|
|
|
<CardTitle>Memory</CardTitle>
|
|
|
|
<TooltipProvider>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button size="icon" variant="ghost">
|
|
|
|
<LuAlertCircle />
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>
|
|
|
|
<p>Overview of used and total memory in frigate process.</p>
|
|
|
|
</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
</TooltipProvider>
|
|
|
|
</div>
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
|
|
<Table>
|
|
|
|
<TableHeader>
|
|
|
|
<TableRow>
|
|
|
|
<TableHead>Location</TableHead>
|
|
|
|
<TableHead>Used</TableHead>
|
|
|
|
<TableHead>Total</TableHead>
|
|
|
|
</TableRow>
|
|
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
|
|
<TableRow>
|
|
|
|
<TableCell>/dev/shm</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{getUnitSize(service["storage"]["/dev/shm"]["used"])}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{getUnitSize(service["storage"]["/dev/shm"]["total"])}
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
<TableRow>
|
|
|
|
<TableCell>/tmp/cache</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{getUnitSize(service["storage"]["/tmp/cache"]["used"])}
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{getUnitSize(service["storage"]["/tmp/cache"]["total"])}
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
</TableBody>
|
|
|
|
</Table>
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
</div>
|
|
|
|
|
2023-12-16 12:06:02 +01:00
|
|
|
<div className="flex items-center my-4">
|
2023-12-14 04:15:10 +01:00
|
|
|
<Heading as="h4">Cameras</Heading>
|
|
|
|
<TooltipProvider>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
<Button size="icon" variant="ghost">
|
|
|
|
<LuAlertCircle />
|
|
|
|
</Button>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>
|
|
|
|
<p>Overview of per-camera storage usage and bandwidth.</p>
|
|
|
|
</TooltipContent>
|
|
|
|
</Tooltip>
|
|
|
|
</TooltipProvider>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-3 gap-4">
|
|
|
|
{Object.entries(storage).map(([name, camera]) => (
|
|
|
|
<Card key={name}>
|
|
|
|
<div className="capitalize text-lg flex justify-between">
|
|
|
|
<Button variant="link">
|
|
|
|
<a className="capitalize" href={`/cameras/${name}`}>
|
|
|
|
{name.replaceAll("_", " ")}
|
|
|
|
</a>
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
<div className="p-2">
|
|
|
|
<Table className="w-full">
|
|
|
|
<TableHeader>
|
|
|
|
<TableRow>
|
|
|
|
<TableCell>Usage</TableCell>
|
|
|
|
<TableCell>Stream Bandwidth</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
</TableHeader>
|
|
|
|
<TableBody>
|
|
|
|
<TableRow>
|
|
|
|
<TableCell>
|
|
|
|
{Math.round(camera["usage_percent"] ?? 0)}%
|
|
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
|
|
{camera["bandwidth"]
|
|
|
|
? `${getUnitSize(camera["bandwidth"])}/hr`
|
|
|
|
: "Calculating..."}
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
</TableBody>
|
|
|
|
</Table>
|
|
|
|
</div>
|
|
|
|
</Card>
|
|
|
|
))}
|
|
|
|
</div>
|
2023-12-08 14:33:22 +01:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default Storage;
|