mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-08 20:25:26 +03:00
Implement label and review filters
This commit is contained in:
parent
01ded3fcc5
commit
335f88f5e6
@ -2396,6 +2396,8 @@ def vod_event(id):
|
||||
@bp.route("/review")
|
||||
def review():
|
||||
cameras = request.args.get("cameras", "all")
|
||||
labels = request.args.get("labels", "all")
|
||||
reviewed = request.args.get("reviewed", default=False)
|
||||
limit = request.args.get("limit", 100)
|
||||
severity = request.args.get("severity", None)
|
||||
|
||||
@ -2410,6 +2412,23 @@ def review():
|
||||
camera_list = cameras.split(",")
|
||||
clauses.append((ReviewSegment.camera << camera_list))
|
||||
|
||||
if labels != "all":
|
||||
# use matching so segments with multiple labels
|
||||
# still match on a search where any label matches
|
||||
label_clauses = []
|
||||
filtered_labels = labels.split(",")
|
||||
|
||||
for label in filtered_labels:
|
||||
label_clauses.append(
|
||||
(ReviewSegment.data["objects"].cast("text") % f'*"{label}"*')
|
||||
)
|
||||
|
||||
label_clause = reduce(operator.or_, label_clauses)
|
||||
clauses.append((label_clause))
|
||||
|
||||
if not reviewed:
|
||||
clauses.append((ReviewSegment.has_been_reviewed == False))
|
||||
|
||||
if severity:
|
||||
clauses.append((ReviewSegment.severity == severity))
|
||||
|
||||
|
||||
@ -8,8 +8,6 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
@ -17,6 +15,8 @@ import { Calendar } from "../ui/calendar";
|
||||
import { ReviewFilter } from "@/types/review";
|
||||
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
|
||||
|
||||
type ReviewFilterGroupProps = {
|
||||
filter?: ReviewFilter;
|
||||
onUpdateFilter: (filter: ReviewFilter) => void;
|
||||
@ -28,9 +28,25 @@ export default function ReviewFilterGroup({
|
||||
}: ReviewFilterGroupProps) {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
const { data: allLabels } = useSWR<string[]>(["labels"], {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
const allLabels = useMemo<string[]>(() => {
|
||||
if (!config) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const labels = new Set<string>();
|
||||
const cameras = filter?.cameras || Object.keys(config.cameras);
|
||||
|
||||
cameras.forEach((camera) => {
|
||||
config.cameras[camera].objects.track.forEach((label) => {
|
||||
if (!ATTRIBUTES.includes(label)) {
|
||||
labels.add(label);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return [...labels];
|
||||
}, [config, filter]);
|
||||
|
||||
const filterValues = useMemo(
|
||||
() => ({
|
||||
cameras: Object.keys(config?.cameras || {}),
|
||||
@ -49,10 +65,17 @@ export default function ReviewFilterGroup({
|
||||
}}
|
||||
/>
|
||||
<CalendarFilterButton before={filter?.before} after={filter?.after} />
|
||||
<Button className="mx-1" variant="secondary">
|
||||
<LuFilter className=" mr-[10px]" />
|
||||
Filter
|
||||
</Button>
|
||||
<GeneralFilterButton
|
||||
allLabels={filterValues.labels}
|
||||
selectedLabels={filter?.labels}
|
||||
updateLabelFilter={(newLabels) => {
|
||||
onUpdateFilter({ ...filter, labels: newLabels });
|
||||
}}
|
||||
showReviewed={filter?.showReviewed || false}
|
||||
setShowReviewed={(reviewed) =>
|
||||
onUpdateFilter({ ...filter, showReviewed: reviewed })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -285,6 +308,7 @@ function CalendarFilterButton({ before, after }: CalendarFilterButtonProps) {
|
||||
future.setFullYear(tomorrow.getFullYear() + 10);
|
||||
return { from: tomorrow, to: future };
|
||||
}, []);
|
||||
// @ts-ignore
|
||||
const dateRange = useMemo(() => {
|
||||
return before == undefined || after == undefined
|
||||
? undefined
|
||||
@ -311,6 +335,118 @@ function CalendarFilterButton({ before, after }: CalendarFilterButtonProps) {
|
||||
);
|
||||
}
|
||||
|
||||
type GeneralFilterButtonProps = {
|
||||
allLabels: string[];
|
||||
selectedLabels: string[] | undefined;
|
||||
updateLabelFilter: (labels: string[] | undefined) => void;
|
||||
showReviewed: boolean;
|
||||
setShowReviewed: (reviewed: boolean) => void;
|
||||
};
|
||||
function GeneralFilterButton({
|
||||
allLabels,
|
||||
selectedLabels,
|
||||
updateLabelFilter,
|
||||
showReviewed,
|
||||
setShowReviewed,
|
||||
}: GeneralFilterButtonProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button className="mx-1" variant="secondary">
|
||||
<LuFilter className=" mr-[10px]" />
|
||||
Filter
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent side="left" asChild>
|
||||
<div className="w-80 flex">
|
||||
<LabelsFilterButton
|
||||
allLabels={allLabels}
|
||||
selectedLabels={selectedLabels}
|
||||
updateLabelFilter={updateLabelFilter}
|
||||
/>
|
||||
<FilterCheckBox
|
||||
label="Show Reviewed"
|
||||
isChecked={showReviewed}
|
||||
onCheckedChange={(isChecked) => setShowReviewed(isChecked)}
|
||||
/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
type LabelFilterButtonProps = {
|
||||
allLabels: string[];
|
||||
selectedLabels: string[] | undefined;
|
||||
updateLabelFilter: (labels: string[] | undefined) => void;
|
||||
};
|
||||
function LabelsFilterButton({
|
||||
allLabels,
|
||||
selectedLabels,
|
||||
updateLabelFilter,
|
||||
}: LabelFilterButtonProps) {
|
||||
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
|
||||
selectedLabels
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
updateLabelFilter(currentLabels);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="mx-1 capitalize" variant="secondary">
|
||||
<LuVideo className=" mr-[10px]" />
|
||||
{selectedLabels == undefined
|
||||
? "All Labels"
|
||||
: `${selectedLabels.length} Labels`}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>Filter Labels</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<FilterCheckBox
|
||||
isChecked={currentLabels == undefined}
|
||||
label="All Labels"
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
setCurrentLabels(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
{allLabels.map((item) => (
|
||||
<FilterCheckBox
|
||||
key={item}
|
||||
isChecked={currentLabels?.includes(item) ?? false}
|
||||
label={item.replaceAll("_", " ")}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
const updatedLabels = currentLabels ? [...currentLabels] : [];
|
||||
|
||||
updatedLabels.push(item);
|
||||
setCurrentLabels(updatedLabels);
|
||||
} else {
|
||||
const updatedLabels = currentLabels ? [...currentLabels] : [];
|
||||
|
||||
// can not deselect the last item
|
||||
if (updatedLabels.length > 1) {
|
||||
updatedLabels.splice(updatedLabels.indexOf(item), 1);
|
||||
setCurrentLabels(updatedLabels);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
type FilterCheckBoxProps = {
|
||||
label: string;
|
||||
isChecked: boolean;
|
||||
|
||||
@ -34,11 +34,14 @@ export default function Events() {
|
||||
|
||||
const getKey = useCallback(
|
||||
(index: number, prevData: ReviewSegment[]) => {
|
||||
console.log("The params are " + JSON.stringify(reviewSearchParams))
|
||||
if (index > 0) {
|
||||
const lastDate = prevData[prevData.length - 1].start_time;
|
||||
reviewSearchParams;
|
||||
const pagedParams = {
|
||||
cameras: reviewSearchParams["cameras"],
|
||||
labels: reviewSearchParams["labels"],
|
||||
reviewed: reviewSearchParams["showReviewed"],
|
||||
before: lastDate,
|
||||
after: reviewSearchParams["after"] || timeRange.after,
|
||||
limit: API_LIMIT,
|
||||
@ -48,6 +51,8 @@ export default function Events() {
|
||||
|
||||
const params = {
|
||||
cameras: reviewSearchParams["cameras"],
|
||||
labels: reviewSearchParams["labels"],
|
||||
reviewed: reviewSearchParams["showReviewed"],
|
||||
limit: API_LIMIT,
|
||||
before: reviewSearchParams["before"] || timeRange.before,
|
||||
after: reviewSearchParams["after"] || timeRange.after,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user