mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-02-14 00:17:05 +01:00
Add ability to filter Explore by Frigate+ submission status (#14909)
* backend * add is_submitted to query params * add submitted filter to dialog * allow is_submitted filter selection with input
This commit is contained in:
parent
c1bfc1df67
commit
0829517b72
@ -47,6 +47,7 @@ class EventsSearchQueryParams(BaseModel):
|
|||||||
time_range: Optional[str] = DEFAULT_TIME_RANGE
|
time_range: Optional[str] = DEFAULT_TIME_RANGE
|
||||||
has_clip: Optional[bool] = None
|
has_clip: Optional[bool] = None
|
||||||
has_snapshot: Optional[bool] = None
|
has_snapshot: Optional[bool] = None
|
||||||
|
is_submitted: Optional[bool] = None
|
||||||
timezone: Optional[str] = "utc"
|
timezone: Optional[str] = "utc"
|
||||||
min_score: Optional[float] = None
|
min_score: Optional[float] = None
|
||||||
max_score: Optional[float] = None
|
max_score: Optional[float] = None
|
||||||
|
@ -360,6 +360,7 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
|
|||||||
time_range = params.time_range
|
time_range = params.time_range
|
||||||
has_clip = params.has_clip
|
has_clip = params.has_clip
|
||||||
has_snapshot = params.has_snapshot
|
has_snapshot = params.has_snapshot
|
||||||
|
is_submitted = params.is_submitted
|
||||||
|
|
||||||
# for similarity search
|
# for similarity search
|
||||||
event_id = params.event_id
|
event_id = params.event_id
|
||||||
@ -441,6 +442,12 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
|
|||||||
if has_snapshot is not None:
|
if has_snapshot is not None:
|
||||||
event_filters.append((Event.has_snapshot == has_snapshot))
|
event_filters.append((Event.has_snapshot == has_snapshot))
|
||||||
|
|
||||||
|
if is_submitted is not None:
|
||||||
|
if is_submitted == 0:
|
||||||
|
event_filters.append((Event.plus_id.is_null()))
|
||||||
|
elif is_submitted > 0:
|
||||||
|
event_filters.append((Event.plus_id != ""))
|
||||||
|
|
||||||
if min_score is not None and max_score is not None:
|
if min_score is not None and max_score is not None:
|
||||||
event_filters.append((Event.data["score"].between(min_score, max_score)))
|
event_filters.append((Event.data["score"].between(min_score, max_score)))
|
||||||
else:
|
else:
|
||||||
|
@ -194,6 +194,11 @@ export default function InputWithTags({
|
|||||||
if (newFilters[filterType] === filterValue) {
|
if (newFilters[filterType] === filterValue) {
|
||||||
delete newFilters[filterType];
|
delete newFilters[filterType];
|
||||||
}
|
}
|
||||||
|
} else if (filterType === "has_snapshot") {
|
||||||
|
if (newFilters[filterType] === filterValue) {
|
||||||
|
delete newFilters[filterType];
|
||||||
|
delete newFilters["is_submitted"];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
delete newFilters[filterType];
|
delete newFilters[filterType];
|
||||||
}
|
}
|
||||||
@ -307,6 +312,10 @@ export default function InputWithTags({
|
|||||||
if (!newFilters.has_snapshot) newFilters.has_snapshot = undefined;
|
if (!newFilters.has_snapshot) newFilters.has_snapshot = undefined;
|
||||||
newFilters.has_snapshot = value == "yes" ? 1 : 0;
|
newFilters.has_snapshot = value == "yes" ? 1 : 0;
|
||||||
break;
|
break;
|
||||||
|
case "is_submitted":
|
||||||
|
if (!newFilters.is_submitted) newFilters.is_submitted = undefined;
|
||||||
|
newFilters.is_submitted = value == "yes" ? 1 : 0;
|
||||||
|
break;
|
||||||
case "has_clip":
|
case "has_clip":
|
||||||
if (!newFilters.has_clip) newFilters.has_clip = undefined;
|
if (!newFilters.has_clip) newFilters.has_clip = undefined;
|
||||||
newFilters.has_clip = value == "yes" ? 1 : 0;
|
newFilters.has_clip = value == "yes" ? 1 : 0;
|
||||||
@ -356,7 +365,11 @@ export default function InputWithTags({
|
|||||||
}`;
|
}`;
|
||||||
} else if (filterType === "min_score" || filterType === "max_score") {
|
} else if (filterType === "min_score" || filterType === "max_score") {
|
||||||
return Math.round(Number(filterValues) * 100).toString() + "%";
|
return Math.round(Number(filterValues) * 100).toString() + "%";
|
||||||
} else if (filterType === "has_clip" || filterType === "has_snapshot") {
|
} else if (
|
||||||
|
filterType === "has_clip" ||
|
||||||
|
filterType === "has_snapshot" ||
|
||||||
|
filterType === "is_submitted"
|
||||||
|
) {
|
||||||
return filterValues ? "Yes" : "No";
|
return filterValues ? "Yes" : "No";
|
||||||
} else {
|
} else {
|
||||||
return filterValues as string;
|
return filterValues as string;
|
||||||
@ -774,7 +787,9 @@ export default function InputWithTags({
|
|||||||
>
|
>
|
||||||
{filterType === "event_id"
|
{filterType === "event_id"
|
||||||
? "Tracked Object ID"
|
? "Tracked Object ID"
|
||||||
: filterType.replaceAll("_", " ")}
|
: filterType === "is_submitted"
|
||||||
|
? "Submitted to Frigate+"
|
||||||
|
: filterType.replaceAll("_", " ")}
|
||||||
: {formatFilterValues(filterType, filterValues)}
|
: {formatFilterValues(filterType, filterValues)}
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
@ -119,6 +119,7 @@ export default function SearchFilterDialog({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<SnapshotClipFilterContent
|
<SnapshotClipFilterContent
|
||||||
|
config={config}
|
||||||
hasSnapshot={
|
hasSnapshot={
|
||||||
currentFilter.has_snapshot !== undefined
|
currentFilter.has_snapshot !== undefined
|
||||||
? currentFilter.has_snapshot === 1
|
? currentFilter.has_snapshot === 1
|
||||||
@ -129,12 +130,19 @@ export default function SearchFilterDialog({
|
|||||||
? currentFilter.has_clip === 1
|
? currentFilter.has_clip === 1
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
setSnapshotClip={(snapshot, clip) =>
|
submittedToFrigatePlus={
|
||||||
|
currentFilter.is_submitted !== undefined
|
||||||
|
? currentFilter.is_submitted === 1
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
setSnapshotClip={(snapshot, clip, submitted) =>
|
||||||
setCurrentFilter({
|
setCurrentFilter({
|
||||||
...currentFilter,
|
...currentFilter,
|
||||||
has_snapshot:
|
has_snapshot:
|
||||||
snapshot !== undefined ? (snapshot ? 1 : 0) : undefined,
|
snapshot !== undefined ? (snapshot ? 1 : 0) : undefined,
|
||||||
has_clip: clip !== undefined ? (clip ? 1 : 0) : undefined,
|
has_clip: clip !== undefined ? (clip ? 1 : 0) : undefined,
|
||||||
|
is_submitted:
|
||||||
|
submitted !== undefined ? (submitted ? 1 : 0) : undefined,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -508,17 +516,22 @@ export function ScoreFilterContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SnapshotClipContentProps = {
|
type SnapshotClipContentProps = {
|
||||||
|
config?: FrigateConfig;
|
||||||
hasSnapshot: boolean | undefined;
|
hasSnapshot: boolean | undefined;
|
||||||
hasClip: boolean | undefined;
|
hasClip: boolean | undefined;
|
||||||
|
submittedToFrigatePlus: boolean | undefined;
|
||||||
setSnapshotClip: (
|
setSnapshotClip: (
|
||||||
snapshot: boolean | undefined,
|
snapshot: boolean | undefined,
|
||||||
clip: boolean | undefined,
|
clip: boolean | undefined,
|
||||||
|
submittedToFrigate: boolean | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function SnapshotClipFilterContent({
|
export function SnapshotClipFilterContent({
|
||||||
|
config,
|
||||||
hasSnapshot,
|
hasSnapshot,
|
||||||
hasClip,
|
hasClip,
|
||||||
|
submittedToFrigatePlus,
|
||||||
setSnapshotClip,
|
setSnapshotClip,
|
||||||
}: SnapshotClipContentProps) {
|
}: SnapshotClipContentProps) {
|
||||||
const [isSnapshotFilterActive, setIsSnapshotFilterActive] = useState(
|
const [isSnapshotFilterActive, setIsSnapshotFilterActive] = useState(
|
||||||
@ -527,6 +540,11 @@ function SnapshotClipFilterContent({
|
|||||||
const [isClipFilterActive, setIsClipFilterActive] = useState(
|
const [isClipFilterActive, setIsClipFilterActive] = useState(
|
||||||
hasClip !== undefined,
|
hasClip !== undefined,
|
||||||
);
|
);
|
||||||
|
const [isFrigatePlusFilterActive, setIsFrigatePlusFilterActive] = useState(
|
||||||
|
submittedToFrigatePlus !== undefined &&
|
||||||
|
isSnapshotFilterActive &&
|
||||||
|
hasSnapshot === true,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsSnapshotFilterActive(hasSnapshot !== undefined);
|
setIsSnapshotFilterActive(hasSnapshot !== undefined);
|
||||||
@ -536,6 +554,14 @@ function SnapshotClipFilterContent({
|
|||||||
setIsClipFilterActive(hasClip !== undefined);
|
setIsClipFilterActive(hasClip !== undefined);
|
||||||
}, [hasClip]);
|
}, [hasClip]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsFrigatePlusFilterActive(
|
||||||
|
submittedToFrigatePlus !== undefined &&
|
||||||
|
isSnapshotFilterActive &&
|
||||||
|
hasSnapshot === true,
|
||||||
|
);
|
||||||
|
}, [submittedToFrigatePlus, isSnapshotFilterActive, hasSnapshot]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-hidden">
|
<div className="overflow-x-hidden">
|
||||||
<DropdownMenuSeparator className="mb-3" />
|
<DropdownMenuSeparator className="mb-3" />
|
||||||
@ -551,9 +577,9 @@ function SnapshotClipFilterContent({
|
|||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
setIsSnapshotFilterActive(checked as boolean);
|
setIsSnapshotFilterActive(checked as boolean);
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setSnapshotClip(true, hasClip);
|
setSnapshotClip(true, hasClip, submittedToFrigatePlus);
|
||||||
} else {
|
} else {
|
||||||
setSnapshotClip(undefined, hasClip);
|
setSnapshotClip(undefined, hasClip, undefined);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -570,8 +596,10 @@ function SnapshotClipFilterContent({
|
|||||||
hasSnapshot === undefined ? undefined : hasSnapshot ? "yes" : "no"
|
hasSnapshot === undefined ? undefined : hasSnapshot ? "yes" : "no"
|
||||||
}
|
}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
if (value === "yes") setSnapshotClip(true, hasClip);
|
if (value === "yes")
|
||||||
else if (value === "no") setSnapshotClip(false, hasClip);
|
setSnapshotClip(true, hasClip, submittedToFrigatePlus);
|
||||||
|
else if (value === "no")
|
||||||
|
setSnapshotClip(false, hasClip, undefined);
|
||||||
}}
|
}}
|
||||||
disabled={!isSnapshotFilterActive}
|
disabled={!isSnapshotFilterActive}
|
||||||
>
|
>
|
||||||
@ -592,6 +620,66 @@ function SnapshotClipFilterContent({
|
|||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{config?.plus?.enabled && (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="plus-filter"
|
||||||
|
className="size-5 text-white accent-white data-[state=checked]:bg-selected data-[state=checked]:text-white"
|
||||||
|
checked={isFrigatePlusFilterActive}
|
||||||
|
disabled={!isSnapshotFilterActive || hasSnapshot !== true}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
setIsFrigatePlusFilterActive(checked as boolean);
|
||||||
|
if (checked) {
|
||||||
|
setSnapshotClip(hasSnapshot, hasClip, true);
|
||||||
|
} else {
|
||||||
|
setSnapshotClip(hasSnapshot, hasClip, undefined);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor="plus-filter"
|
||||||
|
className="cursor-pointer text-sm font-medium leading-none"
|
||||||
|
>
|
||||||
|
Submitted to Frigate+
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
value={
|
||||||
|
submittedToFrigatePlus === undefined
|
||||||
|
? undefined
|
||||||
|
: submittedToFrigatePlus
|
||||||
|
? "yes"
|
||||||
|
: "no"
|
||||||
|
}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
if (value === "yes")
|
||||||
|
setSnapshotClip(hasSnapshot, hasClip, true);
|
||||||
|
else if (value === "no")
|
||||||
|
setSnapshotClip(hasSnapshot, hasClip, false);
|
||||||
|
else setSnapshotClip(hasSnapshot, hasClip, undefined);
|
||||||
|
}}
|
||||||
|
disabled={!isFrigatePlusFilterActive}
|
||||||
|
>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="yes"
|
||||||
|
aria-label="Yes"
|
||||||
|
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem
|
||||||
|
value="no"
|
||||||
|
aria-label="No"
|
||||||
|
className="data-[state=on]:bg-selected data-[state=on]:text-white data-[state=on]:hover:bg-selected data-[state=on]:hover:text-white"
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -601,9 +689,13 @@ function SnapshotClipFilterContent({
|
|||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
setIsClipFilterActive(checked as boolean);
|
setIsClipFilterActive(checked as boolean);
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setSnapshotClip(hasSnapshot, true);
|
setSnapshotClip(hasSnapshot, true, submittedToFrigatePlus);
|
||||||
} else {
|
} else {
|
||||||
setSnapshotClip(hasSnapshot, undefined);
|
setSnapshotClip(
|
||||||
|
hasSnapshot,
|
||||||
|
undefined,
|
||||||
|
submittedToFrigatePlus,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -618,8 +710,10 @@ function SnapshotClipFilterContent({
|
|||||||
type="single"
|
type="single"
|
||||||
value={hasClip === undefined ? undefined : hasClip ? "yes" : "no"}
|
value={hasClip === undefined ? undefined : hasClip ? "yes" : "no"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
if (value === "yes") setSnapshotClip(hasSnapshot, true);
|
if (value === "yes")
|
||||||
else if (value === "no") setSnapshotClip(hasSnapshot, false);
|
setSnapshotClip(hasSnapshot, true, submittedToFrigatePlus);
|
||||||
|
else if (value === "no")
|
||||||
|
setSnapshotClip(hasSnapshot, false, submittedToFrigatePlus);
|
||||||
}}
|
}}
|
||||||
disabled={!isClipFilterActive}
|
disabled={!isClipFilterActive}
|
||||||
>
|
>
|
||||||
|
@ -113,6 +113,7 @@ export default function Explore() {
|
|||||||
min_score: searchSearchParams["min_score"],
|
min_score: searchSearchParams["min_score"],
|
||||||
max_score: searchSearchParams["max_score"],
|
max_score: searchSearchParams["max_score"],
|
||||||
has_snapshot: searchSearchParams["has_snapshot"],
|
has_snapshot: searchSearchParams["has_snapshot"],
|
||||||
|
is_submitted: searchSearchParams["is_submitted"],
|
||||||
has_clip: searchSearchParams["has_clip"],
|
has_clip: searchSearchParams["has_clip"],
|
||||||
event_id: searchSearchParams["event_id"],
|
event_id: searchSearchParams["event_id"],
|
||||||
limit:
|
limit:
|
||||||
@ -144,6 +145,7 @@ export default function Explore() {
|
|||||||
min_score: searchSearchParams["min_score"],
|
min_score: searchSearchParams["min_score"],
|
||||||
max_score: searchSearchParams["max_score"],
|
max_score: searchSearchParams["max_score"],
|
||||||
has_snapshot: searchSearchParams["has_snapshot"],
|
has_snapshot: searchSearchParams["has_snapshot"],
|
||||||
|
is_submitted: searchSearchParams["is_submitted"],
|
||||||
has_clip: searchSearchParams["has_clip"],
|
has_clip: searchSearchParams["has_clip"],
|
||||||
event_id: searchSearchParams["event_id"],
|
event_id: searchSearchParams["event_id"],
|
||||||
timezone,
|
timezone,
|
||||||
|
@ -61,6 +61,7 @@ export type SearchFilter = {
|
|||||||
max_score?: number;
|
max_score?: number;
|
||||||
has_snapshot?: number;
|
has_snapshot?: number;
|
||||||
has_clip?: number;
|
has_clip?: number;
|
||||||
|
is_submitted?: number;
|
||||||
time_range?: string;
|
time_range?: string;
|
||||||
search_type?: SearchSource[];
|
search_type?: SearchSource[];
|
||||||
event_id?: string;
|
event_id?: string;
|
||||||
|
@ -159,8 +159,10 @@ export default function SearchView({
|
|||||||
max_score: ["100"],
|
max_score: ["100"],
|
||||||
has_clip: ["yes", "no"],
|
has_clip: ["yes", "no"],
|
||||||
has_snapshot: ["yes", "no"],
|
has_snapshot: ["yes", "no"],
|
||||||
|
...(config?.plus?.enabled &&
|
||||||
|
searchFilter?.has_snapshot && { is_submitted: ["yes", "no"] }),
|
||||||
}),
|
}),
|
||||||
[config, allLabels, allZones, allSubLabels],
|
[config, allLabels, allZones, allSubLabels, searchFilter],
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove duplicate event ids
|
// remove duplicate event ids
|
||||||
|
Loading…
Reference in New Issue
Block a user