mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-07 03:35:26 +03:00
Add filter popover
This commit is contained in:
parent
229f98a78d
commit
62710bb346
@ -614,20 +614,30 @@ def timeline():
|
||||
@bp.route("/timeline/hourly")
|
||||
def hourly_timeline():
|
||||
"""Get hourly summary for timeline."""
|
||||
camera = request.args.get("camera", "all")
|
||||
cameras = request.args.get("cameras", "all")
|
||||
labels = request.args.get("labels", "all")
|
||||
before = request.args.get("before", type=float)
|
||||
after = request.args.get("after", type=float)
|
||||
limit = request.args.get("limit", 200)
|
||||
tz_name = request.args.get("timezone", default="utc", type=str)
|
||||
_, minute_modifier, _ = get_tz_modifiers(tz_name)
|
||||
|
||||
clauses = []
|
||||
|
||||
if camera != "all":
|
||||
clauses.append((Timeline.camera == camera))
|
||||
if cameras != "all":
|
||||
camera_list = cameras.split(",")
|
||||
clauses.append((Timeline.camera << camera_list))
|
||||
|
||||
if labels != "all":
|
||||
label_list = labels.split(",")
|
||||
clauses.append((Timeline.data["label"] << label_list))
|
||||
|
||||
if before:
|
||||
clauses.append((Timeline.timestamp < before))
|
||||
|
||||
if after:
|
||||
clauses.append((Timeline.timestamp > after))
|
||||
|
||||
if len(clauses) == 0:
|
||||
clauses.append((True))
|
||||
|
||||
|
||||
101
web/src/components/filter/HistoryFilterPopover.tsx
Normal file
101
web/src/components/filter/HistoryFilterPopover.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import { LuFilter } from "react-icons/lu";
|
||||
import { Button } from "../ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||
import useSWR from "swr";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
|
||||
type HistoryFilterPopoverProps = {
|
||||
filter: HistoryFilter;
|
||||
onUpdateFilter: (filter: HistoryFilter) => void;
|
||||
};
|
||||
|
||||
export default function HistoryFilterPopover({
|
||||
filter,
|
||||
onUpdateFilter
|
||||
}: HistoryFilterPopoverProps) {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
const { data: allLabels } = useSWR<string[]>(["labels"], {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
const { data: allSubLabels } = useSWR<string[]>(
|
||||
["sub_labels", { split_joined: 1 }],
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
}
|
||||
);
|
||||
const filterValues = useMemo(
|
||||
() => ({
|
||||
cameras: Object.keys(config?.cameras || {}),
|
||||
labels: Object.values(allLabels || {}),
|
||||
}),
|
||||
[config, allLabels, allSubLabels]
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button>
|
||||
<LuFilter className="mx-1" />
|
||||
Filter
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<div className="grid gap-2 grid-cols-2 md:grid-cols-3">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="capitalize" variant="outline">
|
||||
{""?.replaceAll("_", " ") || "All Cameras"}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Filter Cameras</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{filterValues.cameras.map((item) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
className="capitalize"
|
||||
key={item}
|
||||
checked={
|
||||
filter.cameras.length == 0 || filter.cameras.includes(item)
|
||||
}
|
||||
>
|
||||
{item.replaceAll("_", " ")}
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="capitalize" variant="outline">
|
||||
{""?.replaceAll("_", " ") || "All Labels"}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Filter Labels</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{filterValues.labels.map((item) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
className="capitalize"
|
||||
key={item}
|
||||
checked={
|
||||
filter.labels == undefined || filter.labels.includes(item)
|
||||
}
|
||||
>
|
||||
{item.replaceAll("_", " ")}
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@ -19,6 +19,7 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import HistoryFilterPopover from "@/components/filter/HistoryFilterPopover";
|
||||
|
||||
const API_LIMIT = 200;
|
||||
|
||||
@ -29,6 +30,12 @@ function History() {
|
||||
config?.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
[config]
|
||||
);
|
||||
|
||||
const [searchFilter, setSearchFilter] = useState<HistoryFilter>({
|
||||
cameras: [],
|
||||
labels: [],
|
||||
});
|
||||
|
||||
const timelineFetcher = useCallback((key: any) => {
|
||||
const [path, params] = Array.isArray(key) ? key : [key, undefined];
|
||||
return axios.get(path, { params }).then((res) => res.data);
|
||||
@ -137,7 +144,13 @@ function History() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading as="h2">Review</Heading>
|
||||
<div className="flex justify-between">
|
||||
<Heading as="h2">History</Heading>
|
||||
<HistoryFilterPopover
|
||||
filter={searchFilter}
|
||||
onUpdateFilter={(filter) => setSearchFilter(filter)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AlertDialog
|
||||
open={itemsToDelete != null}
|
||||
|
||||
@ -37,4 +37,9 @@ type HourlyTimeline = {
|
||||
end: number,
|
||||
count: number,
|
||||
hours: { [key: string]: Timeline[] };
|
||||
}
|
||||
|
||||
type HistoryFilter = {
|
||||
cameras: string[],
|
||||
labels: string[],
|
||||
}
|
||||
@ -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'
|
||||
target: 'http://192.168.50.106: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,
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user