mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-11-07 01:20:07 +01:00
121 lines
4.0 KiB
JavaScript
121 lines
4.0 KiB
JavaScript
import { h } from 'preact';
|
|
import { ApiHost } from './context';
|
|
import Box from './components/Box';
|
|
import Heading from './components/Heading';
|
|
import Link from './components/Link';
|
|
import { route } from 'preact-router';
|
|
import { Table, Thead, Tbody, Tfoot, Th, Tr, Td } from './components/Table';
|
|
import { useCallback, useContext, useEffect, useState } from 'preact/hooks';
|
|
|
|
export default function Events({ url } = {}) {
|
|
const apiHost = useContext(ApiHost);
|
|
const [events, setEvents] = useState([]);
|
|
|
|
const searchParams = new URL(`${window.location.protocol}//${window.location.host}${url || '/events'}`).searchParams;
|
|
const searchParamsString = searchParams.toString();
|
|
|
|
useEffect(async () => {
|
|
const response = await fetch(`${apiHost}/api/events?${searchParamsString}`);
|
|
const data = response.ok ? await response.json() : {};
|
|
setEvents(data);
|
|
}, [searchParamsString]);
|
|
|
|
const searchKeys = Array.from(searchParams.keys());
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<Heading>Events</Heading>
|
|
|
|
{searchKeys.length ? (
|
|
<Box>
|
|
<Heading size="sm">Filters</Heading>
|
|
<div className="flex flex-wrap space-x-2">
|
|
{searchKeys.map((filterKey) => (
|
|
<UnFilterable
|
|
paramName={filterKey}
|
|
searchParams={searchParamsString}
|
|
name={`${filterKey}: ${searchParams.get(filterKey)}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</Box>
|
|
) : null}
|
|
|
|
<Box className="min-w-0 overflow-auto">
|
|
<Table>
|
|
<Thead>
|
|
<Tr>
|
|
<Th></Th>
|
|
<Th>Camera</Th>
|
|
<Th>Label</Th>
|
|
<Th>Score</Th>
|
|
<Th>Zones</Th>
|
|
<Th>Date</Th>
|
|
<Th>Start</Th>
|
|
<Th>End</Th>
|
|
</Tr>
|
|
</Thead>
|
|
<Tbody>
|
|
{events.map(
|
|
(
|
|
{ camera, id, label, start_time: startTime, end_time: endTime, thumbnail, top_score: score, zones },
|
|
i
|
|
) => {
|
|
const start = new Date(parseInt(startTime * 1000, 10));
|
|
const end = new Date(parseInt(endTime * 1000, 10));
|
|
return (
|
|
<Tr key={id} index={i}>
|
|
<Td>
|
|
<a href={`/events/${id}`}>
|
|
<img className="w-32 max-w-none" src={`data:image/jpeg;base64,${thumbnail}`} />
|
|
</a>
|
|
</Td>
|
|
<Td>
|
|
<Filterable searchParams={searchParamsString} paramName="camera" name={camera} />
|
|
</Td>
|
|
<Td>
|
|
<Filterable searchParams={searchParamsString} paramName="label" name={label} />
|
|
</Td>
|
|
<Td>{(score * 100).toFixed(2)}%</Td>
|
|
<Td>
|
|
<ul>
|
|
{zones.map((zone) => (
|
|
<li>
|
|
<Filterable searchParams={searchParamsString} paramName="zone" name={zone} />
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</Td>
|
|
<Td>{start.toLocaleDateString()}</Td>
|
|
<Td>{start.toLocaleTimeString()}</Td>
|
|
<Td>{end.toLocaleTimeString()}</Td>
|
|
</Tr>
|
|
);
|
|
}
|
|
)}
|
|
</Tbody>
|
|
</Table>
|
|
</Box>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Filterable({ searchParams, paramName, name }) {
|
|
const params = new URLSearchParams(searchParams);
|
|
params.set(paramName, name);
|
|
return <Link href={`?${params.toString()}`}>{name}</Link>;
|
|
}
|
|
|
|
function UnFilterable({ searchParams, paramName, name }) {
|
|
const params = new URLSearchParams(searchParams);
|
|
params.delete(paramName);
|
|
return (
|
|
<a
|
|
className="bg-gray-700 text-white px-3 py-1 rounded-md hover:bg-gray-300 hover:text-gray-900 dark:bg-gray-300 dark:text-gray-900 dark:hover:bg-gray-700 dark:hover:text-white"
|
|
href={`?${params.toString()}`}
|
|
>
|
|
{name}
|
|
</a>
|
|
);
|
|
}
|