mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-07 11:45:24 +03:00
Add filter popover
This commit is contained in:
parent
229f98a78d
commit
62710bb346
@ -614,20 +614,30 @@ def timeline():
|
|||||||
@bp.route("/timeline/hourly")
|
@bp.route("/timeline/hourly")
|
||||||
def hourly_timeline():
|
def hourly_timeline():
|
||||||
"""Get hourly summary for 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)
|
before = request.args.get("before", type=float)
|
||||||
|
after = request.args.get("after", type=float)
|
||||||
limit = request.args.get("limit", 200)
|
limit = request.args.get("limit", 200)
|
||||||
tz_name = request.args.get("timezone", default="utc", type=str)
|
tz_name = request.args.get("timezone", default="utc", type=str)
|
||||||
_, minute_modifier, _ = get_tz_modifiers(tz_name)
|
_, minute_modifier, _ = get_tz_modifiers(tz_name)
|
||||||
|
|
||||||
clauses = []
|
clauses = []
|
||||||
|
|
||||||
if camera != "all":
|
if cameras != "all":
|
||||||
clauses.append((Timeline.camera == camera))
|
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:
|
if before:
|
||||||
clauses.append((Timeline.timestamp < before))
|
clauses.append((Timeline.timestamp < before))
|
||||||
|
|
||||||
|
if after:
|
||||||
|
clauses.append((Timeline.timestamp > after))
|
||||||
|
|
||||||
if len(clauses) == 0:
|
if len(clauses) == 0:
|
||||||
clauses.append((True))
|
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,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
|
import HistoryFilterPopover from "@/components/filter/HistoryFilterPopover";
|
||||||
|
|
||||||
const API_LIMIT = 200;
|
const API_LIMIT = 200;
|
||||||
|
|
||||||
@ -29,6 +30,12 @@ function History() {
|
|||||||
config?.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
config?.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
[config]
|
[config]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [searchFilter, setSearchFilter] = useState<HistoryFilter>({
|
||||||
|
cameras: [],
|
||||||
|
labels: [],
|
||||||
|
});
|
||||||
|
|
||||||
const timelineFetcher = useCallback((key: any) => {
|
const timelineFetcher = useCallback((key: any) => {
|
||||||
const [path, params] = Array.isArray(key) ? key : [key, undefined];
|
const [path, params] = Array.isArray(key) ? key : [key, undefined];
|
||||||
return axios.get(path, { params }).then((res) => res.data);
|
return axios.get(path, { params }).then((res) => res.data);
|
||||||
@ -137,7 +144,13 @@ function History() {
|
|||||||
|
|
||||||
return (
|
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
|
<AlertDialog
|
||||||
open={itemsToDelete != null}
|
open={itemsToDelete != null}
|
||||||
|
|||||||
@ -37,4 +37,9 @@ type HourlyTimeline = {
|
|||||||
end: number,
|
end: number,
|
||||||
count: number,
|
count: number,
|
||||||
hours: { [key: string]: Timeline[] };
|
hours: { [key: string]: Timeline[] };
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryFilter = {
|
||||||
|
cameras: string[],
|
||||||
|
labels: string[],
|
||||||
}
|
}
|
||||||
@ -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://192.168.50.106: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