mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-08 04:05:26 +03:00
Add table and oveview components
This commit is contained in:
parent
80428a8cc1
commit
01f9fd06b8
117
web-new/src/components/ui/table.tsx
Normal file
117
web-new/src/components/ui/table.tsx
Normal 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,
|
||||||
|
}
|
||||||
@ -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 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() {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Heading as="h2">Storage</Heading>
|
<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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,24 +12,24 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:5000',
|
target: 'http://192.168.50.106:5000',
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
'/vod': {
|
'/vod': {
|
||||||
target: 'http://localhost:5000'
|
target: 'http://192.168.50.106:5000'
|
||||||
},
|
},
|
||||||
'/clips': {
|
'/clips': {
|
||||||
target: 'http://localhost:5000'
|
target: 'http://localhost:5000'
|
||||||
},
|
},
|
||||||
'/exports': {
|
'/exports': {
|
||||||
target: 'http://localhost:5000'
|
target: 'http://192.168.50.106:5000'
|
||||||
},
|
},
|
||||||
'/ws': {
|
'/ws': {
|
||||||
target: 'ws://localhost:5000',
|
target: 'ws://192.168.50.106:5000',
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
'/live': {
|
'/live': {
|
||||||
target: 'ws://localhost:5000',
|
target: 'ws://192.168.50.106:5000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user