Add table and oveview components

This commit is contained in:
Nick Mowen 2023-12-11 18:22:16 -07:00
parent 80428a8cc1
commit 01f9fd06b8
3 changed files with 263 additions and 5 deletions

View File

@ -0,0 +1,117 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@ -1,9 +1,150 @@
import { useWs } from "@/api/ws";
import ActivityIndicator from "@/components/ui/activity-indicator";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import Heading from "@/components/ui/heading";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useMemo } from "react";
import useSWR from "swr";
type CameraStorage = {
[key: string]: {
bandwidth: number;
usage: number;
usage_percent: number;
};
};
const emptyObject = Object.freeze({});
function Storage() {
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"]
);
}, service);
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 />;
}
return (
<>
<Heading as="h2">Storage</Heading>
<Heading as="h3">Overview</Heading>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle>Data</CardTitle>
</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(
service["storage"]["/media/frigate/recordings"]["used"]
)}
</TableCell>
<TableCell>
{getUnitSize(
service["storage"]["/media/frigate/recordings"]["total"]
)}
</TableCell>
</TableRow>
{hasSeparateMedia && (
<TableRow>
<TableCell>Snapshots</TableCell>
<TableCell>
{getUnitSize(
service["storage"]["/media/frigate/clips"]["used"]
)}
</TableCell>
<TableCell>
{getUnitSize(
service["storage"]["/media/frigate/clips"]["total"]
)}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Memory</CardTitle>
</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>
</>
);
}

View File

@ -12,24 +12,24 @@ export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:5000',
target: 'http://192.168.50.106:5000',
ws: true,
},
'/vod': {
target: 'http://localhost:5000'
target: 'http://192.168.50.106:5000'
},
'/clips': {
target: 'http://localhost:5000'
},
'/exports': {
target: 'http://localhost:5000'
target: 'http://192.168.50.106:5000'
},
'/ws': {
target: 'ws://localhost:5000',
target: 'ws://192.168.50.106:5000',
ws: true,
},
'/live': {
target: 'ws://localhost:5000',
target: 'ws://192.168.50.106:5000',
changeOrigin: true,
ws: true,
},