mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-12 13:47:14 +02:00
Review improvements (#11879)
* Update segment even when number of active objects is the same * add score to frigate+ chip * Add support for selecting zones * Add api support for filtering on zones * Adjust UI * Update filtering logic * Clean up
This commit is contained in:
parent
b3eab17f2c
commit
c9d253a320
@ -450,6 +450,7 @@ Reviews from the database. Accepts the following query string parameters:
|
|||||||
| `after` | int | Epoch time |
|
| `after` | int | Epoch time |
|
||||||
| `cameras` | str | , separated list of cameras |
|
| `cameras` | str | , separated list of cameras |
|
||||||
| `labels` | str | , separated list of labels |
|
| `labels` | str | , separated list of labels |
|
||||||
|
| `zones` | str | , separated list of zones |
|
||||||
| `reviewed` | int | Include items that have been reviewed (0 or 1) |
|
| `reviewed` | int | Include items that have been reviewed (0 or 1) |
|
||||||
| `limit` | int | Limit the number of events returned |
|
| `limit` | int | Limit the number of events returned |
|
||||||
| `severity` | str | Limit items to severity (alert, detection, significant_motion) |
|
| `severity` | str | Limit items to severity (alert, detection, significant_motion) |
|
||||||
|
@ -22,6 +22,7 @@ ReviewBp = Blueprint("reviews", __name__)
|
|||||||
def review():
|
def review():
|
||||||
cameras = request.args.get("cameras", "all")
|
cameras = request.args.get("cameras", "all")
|
||||||
labels = request.args.get("labels", "all")
|
labels = request.args.get("labels", "all")
|
||||||
|
zones = request.args.get("zones", "all")
|
||||||
reviewed = request.args.get("reviewed", type=int, default=0)
|
reviewed = request.args.get("reviewed", type=int, default=0)
|
||||||
limit = request.args.get("limit", type=int, default=None)
|
limit = request.args.get("limit", type=int, default=None)
|
||||||
severity = request.args.get("severity", None)
|
severity = request.args.get("severity", None)
|
||||||
@ -60,6 +61,20 @@ def review():
|
|||||||
label_clause = reduce(operator.or_, label_clauses)
|
label_clause = reduce(operator.or_, label_clauses)
|
||||||
clauses.append((label_clause))
|
clauses.append((label_clause))
|
||||||
|
|
||||||
|
if zones != "all":
|
||||||
|
# use matching so segments with multiple zones
|
||||||
|
# still match on a search where any zone matches
|
||||||
|
zone_clauses = []
|
||||||
|
filtered_zones = zones.split(",")
|
||||||
|
|
||||||
|
for zone in filtered_zones:
|
||||||
|
zone_clauses.append(
|
||||||
|
(ReviewSegment.data["zones"].cast("text") % f'*"{zone}"*')
|
||||||
|
)
|
||||||
|
|
||||||
|
zone_clause = reduce(operator.or_, zone_clauses)
|
||||||
|
clauses.append((zone_clause))
|
||||||
|
|
||||||
if reviewed == 0:
|
if reviewed == 0:
|
||||||
clauses.append((ReviewSegment.has_been_reviewed == False))
|
clauses.append((ReviewSegment.has_been_reviewed == False))
|
||||||
|
|
||||||
@ -96,6 +111,7 @@ def review_summary():
|
|||||||
|
|
||||||
cameras = request.args.get("cameras", "all")
|
cameras = request.args.get("cameras", "all")
|
||||||
labels = request.args.get("labels", "all")
|
labels = request.args.get("labels", "all")
|
||||||
|
zones = request.args.get("zones", "all")
|
||||||
|
|
||||||
clauses = [(ReviewSegment.start_time > day_ago)]
|
clauses = [(ReviewSegment.start_time > day_ago)]
|
||||||
|
|
||||||
@ -118,6 +134,20 @@ def review_summary():
|
|||||||
label_clause = reduce(operator.or_, label_clauses)
|
label_clause = reduce(operator.or_, label_clauses)
|
||||||
clauses.append((label_clause))
|
clauses.append((label_clause))
|
||||||
|
|
||||||
|
if zones != "all":
|
||||||
|
# use matching so segments with multiple zones
|
||||||
|
# still match on a search where any zone matches
|
||||||
|
zone_clauses = []
|
||||||
|
filtered_zones = zones.split(",")
|
||||||
|
|
||||||
|
for zone in filtered_zones:
|
||||||
|
zone_clauses.append(
|
||||||
|
(ReviewSegment.data["zones"].cast("text") % f'*"{zone}"*')
|
||||||
|
)
|
||||||
|
|
||||||
|
zone_clause = reduce(operator.or_, zone_clauses)
|
||||||
|
clauses.append((zone_clause))
|
||||||
|
|
||||||
last_24 = (
|
last_24 = (
|
||||||
ReviewSegment.select(
|
ReviewSegment.select(
|
||||||
fn.SUM(
|
fn.SUM(
|
||||||
|
@ -242,6 +242,8 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
active_objects = get_active_objects(frame_time, camera_config, objects)
|
active_objects = get_active_objects(frame_time, camera_config, objects)
|
||||||
|
|
||||||
if len(active_objects) > 0:
|
if len(active_objects) > 0:
|
||||||
|
should_update = False
|
||||||
|
|
||||||
if frame_time > segment.last_update:
|
if frame_time > segment.last_update:
|
||||||
segment.last_update = frame_time
|
segment.last_update = frame_time
|
||||||
|
|
||||||
@ -270,12 +272,16 @@ class ReviewSegmentMaintainer(threading.Thread):
|
|||||||
)
|
)
|
||||||
):
|
):
|
||||||
segment.severity = SeverityEnum.alert
|
segment.severity = SeverityEnum.alert
|
||||||
|
should_update = True
|
||||||
|
|
||||||
# keep zones up to date
|
# keep zones up to date
|
||||||
if len(object["current_zones"]) > 0:
|
if len(object["current_zones"]) > 0:
|
||||||
segment.zones.update(object["current_zones"])
|
segment.zones.update(object["current_zones"])
|
||||||
|
|
||||||
if len(active_objects) > segment.frame_active_count:
|
if len(active_objects) > segment.frame_active_count:
|
||||||
|
should_update = True
|
||||||
|
|
||||||
|
if should_update:
|
||||||
try:
|
try:
|
||||||
frame_id = f"{camera_config.name}{frame_time}"
|
frame_id = f"{camera_config.name}{frame_time}"
|
||||||
yuv_frame = self.frame_manager.get(
|
yuv_frame = self.frame_manager.get(
|
||||||
|
@ -30,6 +30,7 @@ import MobileReviewSettingsDrawer, {
|
|||||||
} from "../overlay/MobileReviewSettingsDrawer";
|
} from "../overlay/MobileReviewSettingsDrawer";
|
||||||
import useOptimisticState from "@/hooks/use-optimistic-state";
|
import useOptimisticState from "@/hooks/use-optimistic-state";
|
||||||
import FilterSwitch from "./FilterSwitch";
|
import FilterSwitch from "./FilterSwitch";
|
||||||
|
import { FilterList } from "@/types/filter";
|
||||||
|
|
||||||
const REVIEW_FILTERS = [
|
const REVIEW_FILTERS = [
|
||||||
"cameras",
|
"cameras",
|
||||||
@ -53,7 +54,7 @@ type ReviewFilterGroupProps = {
|
|||||||
reviewSummary?: ReviewSummary;
|
reviewSummary?: ReviewSummary;
|
||||||
filter?: ReviewFilter;
|
filter?: ReviewFilter;
|
||||||
motionOnly: boolean;
|
motionOnly: boolean;
|
||||||
filterLabels?: string[];
|
filterList?: FilterList;
|
||||||
onUpdateFilter: (filter: ReviewFilter) => void;
|
onUpdateFilter: (filter: ReviewFilter) => void;
|
||||||
setMotionOnly: React.Dispatch<React.SetStateAction<boolean>>;
|
setMotionOnly: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
};
|
};
|
||||||
@ -64,15 +65,15 @@ export default function ReviewFilterGroup({
|
|||||||
reviewSummary,
|
reviewSummary,
|
||||||
filter,
|
filter,
|
||||||
motionOnly,
|
motionOnly,
|
||||||
filterLabels,
|
filterList,
|
||||||
onUpdateFilter,
|
onUpdateFilter,
|
||||||
setMotionOnly,
|
setMotionOnly,
|
||||||
}: ReviewFilterGroupProps) {
|
}: ReviewFilterGroupProps) {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
const allLabels = useMemo<string[]>(() => {
|
const allLabels = useMemo<string[]>(() => {
|
||||||
if (filterLabels) {
|
if (filterList?.labels) {
|
||||||
return filterLabels;
|
return filterList.labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@ -99,14 +100,43 @@ export default function ReviewFilterGroup({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return [...labels].sort();
|
return [...labels].sort();
|
||||||
}, [config, filterLabels, filter]);
|
}, [config, filterList, filter]);
|
||||||
|
|
||||||
|
const allZones = useMemo<string[]>(() => {
|
||||||
|
if (filterList?.zones) {
|
||||||
|
return filterList.zones;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const zones = new Set<string>();
|
||||||
|
const cameras = filter?.cameras || Object.keys(config.cameras);
|
||||||
|
|
||||||
|
cameras.forEach((camera) => {
|
||||||
|
if (camera == "birdseye") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cameraConfig = config.cameras[camera];
|
||||||
|
cameraConfig.review.alerts.required_zones.forEach((zone) => {
|
||||||
|
zones.add(zone);
|
||||||
|
});
|
||||||
|
cameraConfig.review.detections.required_zones.forEach((zone) => {
|
||||||
|
zones.add(zone);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...zones].sort();
|
||||||
|
}, [config, filterList, filter]);
|
||||||
|
|
||||||
const filterValues = useMemo(
|
const filterValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
cameras: Object.keys(config?.cameras || {}),
|
cameras: Object.keys(config?.cameras || {}),
|
||||||
labels: Object.values(allLabels || {}),
|
labels: Object.values(allLabels || {}),
|
||||||
|
zones: Object.values(allZones || {}),
|
||||||
}),
|
}),
|
||||||
[config, allLabels],
|
[config, allLabels, allZones],
|
||||||
);
|
);
|
||||||
|
|
||||||
const groups = useMemo(() => {
|
const groups = useMemo(() => {
|
||||||
@ -189,12 +219,17 @@ export default function ReviewFilterGroup({
|
|||||||
selectedLabels={filter?.labels}
|
selectedLabels={filter?.labels}
|
||||||
currentSeverity={currentSeverity}
|
currentSeverity={currentSeverity}
|
||||||
showAll={filter?.showAll == true}
|
showAll={filter?.showAll == true}
|
||||||
|
allZones={filterValues.zones}
|
||||||
|
selectedZones={filter?.zones}
|
||||||
setShowAll={(showAll) => {
|
setShowAll={(showAll) => {
|
||||||
onUpdateFilter({ ...filter, showAll });
|
onUpdateFilter({ ...filter, showAll });
|
||||||
}}
|
}}
|
||||||
updateLabelFilter={(newLabels) => {
|
updateLabelFilter={(newLabels) => {
|
||||||
onUpdateFilter({ ...filter, labels: newLabels });
|
onUpdateFilter({ ...filter, labels: newLabels });
|
||||||
}}
|
}}
|
||||||
|
updateZoneFilter={(newZones) =>
|
||||||
|
onUpdateFilter({ ...filter, zones: newZones })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isMobile && mobileSettingsFeatures.length > 0 && (
|
{isMobile && mobileSettingsFeatures.length > 0 && (
|
||||||
@ -204,6 +239,7 @@ export default function ReviewFilterGroup({
|
|||||||
currentSeverity={currentSeverity}
|
currentSeverity={currentSeverity}
|
||||||
reviewSummary={reviewSummary}
|
reviewSummary={reviewSummary}
|
||||||
allLabels={allLabels}
|
allLabels={allLabels}
|
||||||
|
allZones={allZones}
|
||||||
onUpdateFilter={onUpdateFilter}
|
onUpdateFilter={onUpdateFilter}
|
||||||
// not applicable as exports are not used
|
// not applicable as exports are not used
|
||||||
camera=""
|
camera=""
|
||||||
@ -495,21 +531,30 @@ type GeneralFilterButtonProps = {
|
|||||||
selectedLabels: string[] | undefined;
|
selectedLabels: string[] | undefined;
|
||||||
currentSeverity?: ReviewSeverity;
|
currentSeverity?: ReviewSeverity;
|
||||||
showAll: boolean;
|
showAll: boolean;
|
||||||
|
allZones: string[];
|
||||||
|
selectedZones?: string[];
|
||||||
setShowAll: (showAll: boolean) => void;
|
setShowAll: (showAll: boolean) => void;
|
||||||
updateLabelFilter: (labels: string[] | undefined) => void;
|
updateLabelFilter: (labels: string[] | undefined) => void;
|
||||||
|
updateZoneFilter: (zones: string[] | undefined) => void;
|
||||||
};
|
};
|
||||||
function GeneralFilterButton({
|
function GeneralFilterButton({
|
||||||
allLabels,
|
allLabels,
|
||||||
selectedLabels,
|
selectedLabels,
|
||||||
currentSeverity,
|
currentSeverity,
|
||||||
showAll,
|
showAll,
|
||||||
|
allZones,
|
||||||
|
selectedZones,
|
||||||
setShowAll,
|
setShowAll,
|
||||||
updateLabelFilter,
|
updateLabelFilter,
|
||||||
|
updateZoneFilter,
|
||||||
}: GeneralFilterButtonProps) {
|
}: GeneralFilterButtonProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
|
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
|
||||||
selectedLabels,
|
selectedLabels,
|
||||||
);
|
);
|
||||||
|
const [currentZones, setCurrentZones] = useState<string[] | undefined>(
|
||||||
|
selectedZones,
|
||||||
|
);
|
||||||
|
|
||||||
const trigger = (
|
const trigger = (
|
||||||
<Button
|
<Button
|
||||||
@ -534,6 +579,11 @@ function GeneralFilterButton({
|
|||||||
currentLabels={currentLabels}
|
currentLabels={currentLabels}
|
||||||
currentSeverity={currentSeverity}
|
currentSeverity={currentSeverity}
|
||||||
showAll={showAll}
|
showAll={showAll}
|
||||||
|
allZones={allZones}
|
||||||
|
selectedZones={selectedZones}
|
||||||
|
currentZones={currentZones}
|
||||||
|
setCurrentZones={setCurrentZones}
|
||||||
|
updateZoneFilter={updateZoneFilter}
|
||||||
setShowAll={setShowAll}
|
setShowAll={setShowAll}
|
||||||
updateLabelFilter={updateLabelFilter}
|
updateLabelFilter={updateLabelFilter}
|
||||||
setCurrentLabels={setCurrentLabels}
|
setCurrentLabels={setCurrentLabels}
|
||||||
@ -584,9 +634,14 @@ type GeneralFilterContentProps = {
|
|||||||
currentLabels: string[] | undefined;
|
currentLabels: string[] | undefined;
|
||||||
currentSeverity?: ReviewSeverity;
|
currentSeverity?: ReviewSeverity;
|
||||||
showAll?: boolean;
|
showAll?: boolean;
|
||||||
|
allZones?: string[];
|
||||||
|
selectedZones?: string[];
|
||||||
|
currentZones?: string[];
|
||||||
setShowAll?: (showAll: boolean) => void;
|
setShowAll?: (showAll: boolean) => void;
|
||||||
updateLabelFilter: (labels: string[] | undefined) => void;
|
updateLabelFilter: (labels: string[] | undefined) => void;
|
||||||
setCurrentLabels: (labels: string[] | undefined) => void;
|
setCurrentLabels: (labels: string[] | undefined) => void;
|
||||||
|
updateZoneFilter?: (zones: string[] | undefined) => void;
|
||||||
|
setCurrentZones?: (zones: string[] | undefined) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
export function GeneralFilterContent({
|
export function GeneralFilterContent({
|
||||||
@ -595,9 +650,14 @@ export function GeneralFilterContent({
|
|||||||
currentLabels,
|
currentLabels,
|
||||||
currentSeverity,
|
currentSeverity,
|
||||||
showAll,
|
showAll,
|
||||||
|
allZones,
|
||||||
|
selectedZones,
|
||||||
|
currentZones,
|
||||||
setShowAll,
|
setShowAll,
|
||||||
updateLabelFilter,
|
updateLabelFilter,
|
||||||
setCurrentLabels,
|
setCurrentLabels,
|
||||||
|
updateZoneFilter,
|
||||||
|
setCurrentZones,
|
||||||
onClose,
|
onClose,
|
||||||
}: GeneralFilterContentProps) {
|
}: GeneralFilterContentProps) {
|
||||||
return (
|
return (
|
||||||
@ -622,7 +682,7 @@ export function GeneralFilterContent({
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="my-2.5 flex items-center justify-between">
|
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||||
<Label
|
<Label
|
||||||
className="mx-2 cursor-pointer text-primary"
|
className="mx-2 cursor-pointer text-primary"
|
||||||
htmlFor="allLabels"
|
htmlFor="allLabels"
|
||||||
@ -640,7 +700,6 @@ export function GeneralFilterContent({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<div className="my-2.5 flex flex-col gap-2.5">
|
<div className="my-2.5 flex flex-col gap-2.5">
|
||||||
{allLabels.map((item) => (
|
{allLabels.map((item) => (
|
||||||
<FilterSwitch
|
<FilterSwitch
|
||||||
@ -666,6 +725,53 @@ export function GeneralFilterContent({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{allZones && setCurrentZones && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<div className="mb-5 mt-2.5 flex items-center justify-between">
|
||||||
|
<Label
|
||||||
|
className="mx-2 cursor-pointer text-primary"
|
||||||
|
htmlFor="allZones"
|
||||||
|
>
|
||||||
|
All Zones
|
||||||
|
</Label>
|
||||||
|
<Switch
|
||||||
|
className="ml-1"
|
||||||
|
id="allZones"
|
||||||
|
checked={currentZones == undefined}
|
||||||
|
onCheckedChange={(isChecked) => {
|
||||||
|
if (isChecked) {
|
||||||
|
setCurrentZones(undefined);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="my-2.5 flex flex-col gap-2.5">
|
||||||
|
{allZones.map((item) => (
|
||||||
|
<FilterSwitch
|
||||||
|
label={item.replaceAll("_", " ")}
|
||||||
|
isChecked={currentZones?.includes(item) ?? false}
|
||||||
|
onCheckedChange={(isChecked) => {
|
||||||
|
if (isChecked) {
|
||||||
|
const updatedZones = currentZones ? [...currentZones] : [];
|
||||||
|
|
||||||
|
updatedZones.push(item);
|
||||||
|
setCurrentZones(updatedZones);
|
||||||
|
} else {
|
||||||
|
const updatedZones = currentZones ? [...currentZones] : [];
|
||||||
|
|
||||||
|
// can not deselect the last item
|
||||||
|
if (updatedZones.length > 1) {
|
||||||
|
updatedZones.splice(updatedZones.indexOf(item), 1);
|
||||||
|
setCurrentZones(updatedZones);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<div className="flex items-center justify-evenly p-2">
|
<div className="flex items-center justify-evenly p-2">
|
||||||
<Button
|
<Button
|
||||||
@ -675,6 +781,10 @@ export function GeneralFilterContent({
|
|||||||
updateLabelFilter(currentLabels);
|
updateLabelFilter(currentLabels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (updateZoneFilter && selectedZones != currentZones) {
|
||||||
|
updateZoneFilter(currentZones);
|
||||||
|
}
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -36,6 +36,7 @@ type MobileReviewSettingsDrawerProps = {
|
|||||||
mode: ExportMode;
|
mode: ExportMode;
|
||||||
reviewSummary?: ReviewSummary;
|
reviewSummary?: ReviewSummary;
|
||||||
allLabels: string[];
|
allLabels: string[];
|
||||||
|
allZones: string[];
|
||||||
onUpdateFilter: (filter: ReviewFilter) => void;
|
onUpdateFilter: (filter: ReviewFilter) => void;
|
||||||
setRange: (range: TimeRange | undefined) => void;
|
setRange: (range: TimeRange | undefined) => void;
|
||||||
setMode: (mode: ExportMode) => void;
|
setMode: (mode: ExportMode) => void;
|
||||||
@ -51,6 +52,7 @@ export default function MobileReviewSettingsDrawer({
|
|||||||
mode,
|
mode,
|
||||||
reviewSummary,
|
reviewSummary,
|
||||||
allLabels,
|
allLabels,
|
||||||
|
allZones,
|
||||||
onUpdateFilter,
|
onUpdateFilter,
|
||||||
setRange,
|
setRange,
|
||||||
setMode,
|
setMode,
|
||||||
@ -104,6 +106,9 @@ export default function MobileReviewSettingsDrawer({
|
|||||||
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
|
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
|
||||||
filter?.labels,
|
filter?.labels,
|
||||||
);
|
);
|
||||||
|
const [currentZones, setCurrentZones] = useState<string[] | undefined>(
|
||||||
|
filter?.zones,
|
||||||
|
);
|
||||||
|
|
||||||
if (!isMobile) {
|
if (!isMobile) {
|
||||||
return;
|
return;
|
||||||
@ -222,7 +227,7 @@ export default function MobileReviewSettingsDrawer({
|
|||||||
);
|
);
|
||||||
} else if (drawerMode == "filter") {
|
} else if (drawerMode == "filter") {
|
||||||
content = (
|
content = (
|
||||||
<div className="scrollbar-container flex h-auto w-full flex-col overflow-y-auto">
|
<div className="scrollbar-container flex h-auto w-full flex-col overflow-y-auto overflow-x-hidden">
|
||||||
<div className="relative mb-2 h-8 w-full">
|
<div className="relative mb-2 h-8 w-full">
|
||||||
<div
|
<div
|
||||||
className="absolute left-0 text-selected"
|
className="absolute left-0 text-selected"
|
||||||
@ -240,6 +245,13 @@ export default function MobileReviewSettingsDrawer({
|
|||||||
currentLabels={currentLabels}
|
currentLabels={currentLabels}
|
||||||
currentSeverity={currentSeverity}
|
currentSeverity={currentSeverity}
|
||||||
showAll={filter?.showAll == true}
|
showAll={filter?.showAll == true}
|
||||||
|
allZones={allZones}
|
||||||
|
selectedZones={filter?.zones}
|
||||||
|
currentZones={currentZones}
|
||||||
|
setCurrentZones={setCurrentZones}
|
||||||
|
updateZoneFilter={(newZones) =>
|
||||||
|
onUpdateFilter({ ...filter, zones: newZones })
|
||||||
|
}
|
||||||
setShowAll={(showAll) => {
|
setShowAll={(showAll) => {
|
||||||
onUpdateFilter({ ...filter, showAll });
|
onUpdateFilter({ ...filter, showAll });
|
||||||
}}
|
}}
|
||||||
|
@ -136,6 +136,7 @@ export default function Events() {
|
|||||||
const params = {
|
const params = {
|
||||||
cameras: reviewSearchParams["cameras"],
|
cameras: reviewSearchParams["cameras"],
|
||||||
labels: reviewSearchParams["labels"],
|
labels: reviewSearchParams["labels"],
|
||||||
|
zones: reviewSearchParams["zones"],
|
||||||
reviewed: 1,
|
reviewed: 1,
|
||||||
before: reviewSearchParams["before"] || last24Hours.before,
|
before: reviewSearchParams["before"] || last24Hours.before,
|
||||||
after: reviewSearchParams["after"] || last24Hours.after,
|
after: reviewSearchParams["after"] || last24Hours.after,
|
||||||
@ -221,6 +222,7 @@ export default function Events() {
|
|||||||
timezone: timezone,
|
timezone: timezone,
|
||||||
cameras: reviewSearchParams["cameras"] ?? null,
|
cameras: reviewSearchParams["cameras"] ?? null,
|
||||||
labels: reviewSearchParams["labels"] ?? null,
|
labels: reviewSearchParams["labels"] ?? null,
|
||||||
|
zones: reviewSearchParams["zones"] ?? null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
|
@ -293,7 +293,7 @@ export default function SubmitPlus() {
|
|||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div className="mx-3 pb-1 text-sm text-white">
|
<div className="mx-3 pb-1 text-sm text-white">
|
||||||
<Chip
|
<Chip
|
||||||
className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500`}
|
className={`z-0 flex items-center justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500`}
|
||||||
>
|
>
|
||||||
{[event.label].map((object) => {
|
{[event.label].map((object) => {
|
||||||
return getIconForLabel(
|
return getIconForLabel(
|
||||||
@ -301,6 +301,9 @@ export default function SubmitPlus() {
|
|||||||
"size-3 text-white",
|
"size-3 text-white",
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<div className="text-xs">
|
||||||
|
{Math.round(event.data.score * 100)}%
|
||||||
|
</div>
|
||||||
</Chip>
|
</Chip>
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
|
@ -3,3 +3,8 @@
|
|||||||
export type FilterType = { [searchKey: string]: any };
|
export type FilterType = { [searchKey: string]: any };
|
||||||
|
|
||||||
export type ExportMode = "select" | "timeline" | "none";
|
export type ExportMode = "select" | "timeline" | "none";
|
||||||
|
|
||||||
|
export type FilterList = {
|
||||||
|
labels?: string[];
|
||||||
|
zones?: string[];
|
||||||
|
};
|
||||||
|
@ -32,6 +32,7 @@ export type SegmentedReviewData =
|
|||||||
export type ReviewFilter = {
|
export type ReviewFilter = {
|
||||||
cameras?: string[];
|
cameras?: string[];
|
||||||
labels?: string[];
|
labels?: string[];
|
||||||
|
zones?: string[];
|
||||||
before?: number;
|
before?: number;
|
||||||
after?: number;
|
after?: number;
|
||||||
showReviewed?: 0 | 1;
|
showReviewed?: 0 | 1;
|
||||||
|
@ -49,6 +49,7 @@ import scrollIntoView from "scroll-into-view-if-needed";
|
|||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { FilterList } from "@/types/filter";
|
||||||
|
|
||||||
type EventViewProps = {
|
type EventViewProps = {
|
||||||
reviewItems?: SegmentedReviewData;
|
reviewItems?: SegmentedReviewData;
|
||||||
@ -203,8 +204,9 @@ export default function EventView({
|
|||||||
|
|
||||||
// review filter info
|
// review filter info
|
||||||
|
|
||||||
const reviewLabels = useMemo(() => {
|
const reviewFilterList = useMemo<FilterList>(() => {
|
||||||
const uniqueLabels = new Set<string>();
|
const uniqueLabels = new Set<string>();
|
||||||
|
const uniqueZones = new Set<string>();
|
||||||
|
|
||||||
reviewItems?.all?.forEach((rev) => {
|
reviewItems?.all?.forEach((rev) => {
|
||||||
rev.data.objects.forEach((obj) =>
|
rev.data.objects.forEach((obj) =>
|
||||||
@ -213,7 +215,11 @@ export default function EventView({
|
|||||||
rev.data.audio.forEach((aud) => uniqueLabels.add(aud));
|
rev.data.audio.forEach((aud) => uniqueLabels.add(aud));
|
||||||
});
|
});
|
||||||
|
|
||||||
return [...uniqueLabels];
|
reviewItems?.all?.forEach((rev) => {
|
||||||
|
rev.data.zones.forEach((zone) => uniqueZones.add(zone));
|
||||||
|
});
|
||||||
|
|
||||||
|
return { labels: [...uniqueLabels], zones: [...uniqueZones] };
|
||||||
}, [reviewItems]);
|
}, [reviewItems]);
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
@ -282,7 +288,7 @@ export default function EventView({
|
|||||||
reviewSummary={reviewSummary}
|
reviewSummary={reviewSummary}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
motionOnly={motionOnly}
|
motionOnly={motionOnly}
|
||||||
filterLabels={reviewLabels}
|
filterList={reviewFilterList}
|
||||||
onUpdateFilter={updateFilter}
|
onUpdateFilter={updateFilter}
|
||||||
setMotionOnly={setMotionOnly}
|
setMotionOnly={setMotionOnly}
|
||||||
/>
|
/>
|
||||||
|
@ -111,7 +111,7 @@ export function RecordingView({
|
|||||||
() => chunkedTimeRange[selectedRangeIdx],
|
() => chunkedTimeRange[selectedRangeIdx],
|
||||||
[selectedRangeIdx, chunkedTimeRange],
|
[selectedRangeIdx, chunkedTimeRange],
|
||||||
);
|
);
|
||||||
const reviewLabels = useMemo(() => {
|
const reviewFilterList = useMemo(() => {
|
||||||
const uniqueLabels = new Set<string>();
|
const uniqueLabels = new Set<string>();
|
||||||
|
|
||||||
reviewItems?.forEach((rev) => {
|
reviewItems?.forEach((rev) => {
|
||||||
@ -121,7 +121,13 @@ export function RecordingView({
|
|||||||
rev.data.audio.forEach((aud) => uniqueLabels.add(aud));
|
rev.data.audio.forEach((aud) => uniqueLabels.add(aud));
|
||||||
});
|
});
|
||||||
|
|
||||||
return [...uniqueLabels];
|
const uniqueZones = new Set<string>();
|
||||||
|
|
||||||
|
reviewItems?.forEach((rev) => {
|
||||||
|
rev.data.zones.forEach((zone) => uniqueZones.add(zone));
|
||||||
|
});
|
||||||
|
|
||||||
|
return { labels: [...uniqueLabels], zones: [...uniqueZones] };
|
||||||
}, [reviewItems]);
|
}, [reviewItems]);
|
||||||
|
|
||||||
// export
|
// export
|
||||||
@ -391,7 +397,7 @@ export function RecordingView({
|
|||||||
reviewSummary={reviewSummary}
|
reviewSummary={reviewSummary}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
motionOnly={false}
|
motionOnly={false}
|
||||||
filterLabels={reviewLabels}
|
filterList={reviewFilterList}
|
||||||
onUpdateFilter={updateFilter}
|
onUpdateFilter={updateFilter}
|
||||||
setMotionOnly={() => {}}
|
setMotionOnly={() => {}}
|
||||||
/>
|
/>
|
||||||
@ -434,7 +440,8 @@ export function RecordingView({
|
|||||||
latestTime={timeRange.before}
|
latestTime={timeRange.before}
|
||||||
mode={exportMode}
|
mode={exportMode}
|
||||||
range={exportRange}
|
range={exportRange}
|
||||||
allLabels={reviewLabels}
|
allLabels={reviewFilterList.labels}
|
||||||
|
allZones={reviewFilterList.zones}
|
||||||
onUpdateFilter={updateFilter}
|
onUpdateFilter={updateFilter}
|
||||||
setRange={setExportRange}
|
setRange={setExportRange}
|
||||||
setMode={setExportMode}
|
setMode={setExportMode}
|
||||||
|
Loading…
Reference in New Issue
Block a user