require all masks and zones to be on the base config

This commit is contained in:
Josh Hawkins 2026-04-02 08:53:20 -05:00
parent a7761225bc
commit b116f61055
3 changed files with 131 additions and 53 deletions

View File

@ -539,6 +539,7 @@
}, },
"restart_required": "Restart required (masks/zones changed)", "restart_required": "Restart required (masks/zones changed)",
"disabledInConfig": "Item is disabled in the config file", "disabledInConfig": "Item is disabled in the config file",
"addDisabledProfile": "Add to the base config first, then override in the profile",
"profileBase": "(base)", "profileBase": "(base)",
"profileOverride": "(override)", "profileOverride": "(override)",
"toast": { "toast": {

View File

@ -154,7 +154,19 @@ export default function PolygonItem({
cameraConfig?.review.alerts.required_zones || [], cameraConfig?.review.alerts.required_zones || [],
cameraConfig?.review.detections.required_zones || [], cameraConfig?.review.detections.required_zones || [],
); );
url = `cameras.${polygon.camera}.zones.${polygon.name}${alertQueries}${detectionQueries}`; // Also delete from profiles that have overrides for this zone
let profileQueries = "";
if (allProfileNames && cameraConfig) {
for (const profileName of allProfileNames) {
if (
cameraConfig.profiles?.[profileName]?.zones?.[polygon.name] !==
undefined
) {
profileQueries += `&cameras.${polygon.camera}.profiles.${profileName}.zones.${polygon.name}`;
}
}
}
url = `cameras.${polygon.camera}.zones.${polygon.name}${alertQueries}${detectionQueries}${profileQueries}`;
} }
await axios await axios
@ -214,11 +226,41 @@ export default function PolygonItem({
}, },
}; };
let cameraUpdate: Record<string, unknown>;
if (editingProfile) {
cameraUpdate = { profiles: { [editingProfile]: deleteSection } };
} else {
// Base mode: also delete from profiles that have overrides for this mask
const profileDeletes: Record<string, unknown> = {};
if (allProfileNames && cameraConfig) {
for (const profileName of allProfileNames) {
const profileData = cameraConfig.profiles?.[profileName];
if (!profileData) continue;
const hasMask =
polygon.type === "motion_mask"
? profileData.motion?.mask?.[polygon.name] !== undefined
: polygon.type === "object_mask"
? profileData.objects?.mask?.[polygon.name] !== undefined ||
Object.values(profileData.objects?.filters || {}).some(
(f) => f?.mask?.[polygon.name] !== undefined,
)
: false;
if (hasMask) {
profileDeletes[profileName] = deleteSection;
}
}
}
cameraUpdate =
Object.keys(profileDeletes).length > 0
? { ...deleteSection, profiles: profileDeletes }
: deleteSection;
}
const configUpdate = { const configUpdate = {
cameras: { cameras: {
[polygon.camera]: editingProfile [polygon.camera]: cameraUpdate,
? { profiles: { [editingProfile]: deleteSection } }
: deleteSection,
}, },
}; };
@ -271,6 +313,7 @@ export default function PolygonItem({
index, index,
setLoadingPolygonIndex, setLoadingPolygonIndex,
editingProfile, editingProfile,
allProfileNames,
onDeleted, onDeleted,
], ],
); );
@ -430,22 +473,41 @@ export default function PolygonItem({
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle> <AlertDialogTitle>
{t("masksAndZones.form.polygonDrawing.delete.title")} {polygon.polygonSource === "override"
? t(
"masksAndZones.form.polygonDrawing.revertOverride.title",
)
: t("masksAndZones.form.polygonDrawing.delete.title")}
</AlertDialogTitle> </AlertDialogTitle>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogDescription> <AlertDialogDescription>
<Trans {polygon.polygonSource === "override" ? (
ns="views/settings" <Trans
values={{ ns="views/settings"
type: t( values={{
`masksAndZones.form.polygonDrawing.type.${polygon.type}`, type: t(
{ ns: "views/settings" }, `masksAndZones.form.polygonDrawing.type.${polygon.type}`,
), { ns: "views/settings" },
name: polygon.friendly_name ?? polygon.name, ),
}} name: polygon.friendly_name ?? polygon.name,
> }}
masksAndZones.form.polygonDrawing.delete.desc >
</Trans> masksAndZones.form.polygonDrawing.revertOverride.desc
</Trans>
) : (
<Trans
ns="views/settings"
values={{
type: t(
`masksAndZones.form.polygonDrawing.type.${polygon.type}`,
{ ns: "views/settings" },
),
name: polygon.friendly_name ?? polygon.name,
}}
>
masksAndZones.form.polygonDrawing.delete.desc
</Trans>
)}
</AlertDialogDescription> </AlertDialogDescription>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel> <AlertDialogCancel>

View File

@ -816,20 +816,25 @@ export default function MasksAndZonesView({
</HoverCard> </HoverCard>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <span className="inline-flex">
variant="secondary" <Button
className="size-6 rounded-md bg-secondary-foreground p-1 text-background" variant="secondary"
aria-label={t("masksAndZones.zones.add")} className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
onClick={() => { aria-label={t("masksAndZones.zones.add")}
setEditPane("zone"); disabled={!!currentEditingProfile}
handleNewPolygon("zone"); onClick={() => {
}} setEditPane("zone");
> handleNewPolygon("zone");
<LuPlus /> }}
</Button> >
<LuPlus />
</Button>
</span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{t("masksAndZones.zones.add")} {currentEditingProfile
? t("masksAndZones.addDisabledProfile")
: t("masksAndZones.zones.add")}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
@ -891,20 +896,25 @@ export default function MasksAndZonesView({
</HoverCard> </HoverCard>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <span className="inline-flex">
variant="secondary" <Button
className="size-6 rounded-md bg-secondary-foreground p-1 text-background" variant="secondary"
aria-label={t("masksAndZones.motionMasks.add")} className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
onClick={() => { aria-label={t("masksAndZones.motionMasks.add")}
setEditPane("motion_mask"); disabled={!!currentEditingProfile}
handleNewPolygon("motion_mask"); onClick={() => {
}} setEditPane("motion_mask");
> handleNewPolygon("motion_mask");
<LuPlus /> }}
</Button> >
<LuPlus />
</Button>
</span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{t("masksAndZones.motionMasks.add")} {currentEditingProfile
? t("masksAndZones.addDisabledProfile")
: t("masksAndZones.motionMasks.add")}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
@ -968,20 +978,25 @@ export default function MasksAndZonesView({
</HoverCard> </HoverCard>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <span className="inline-flex">
variant="secondary" <Button
className="size-6 rounded-md bg-secondary-foreground p-1 text-background" variant="secondary"
aria-label={t("masksAndZones.objectMasks.add")} className="size-6 rounded-md bg-secondary-foreground p-1 text-background"
onClick={() => { aria-label={t("masksAndZones.objectMasks.add")}
setEditPane("object_mask"); disabled={!!currentEditingProfile}
handleNewPolygon("object_mask"); onClick={() => {
}} setEditPane("object_mask");
> handleNewPolygon("object_mask");
<LuPlus /> }}
</Button> >
<LuPlus />
</Button>
</span>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{t("masksAndZones.objectMasks.add")} {currentEditingProfile
? t("masksAndZones.addDisabledProfile")
: t("masksAndZones.objectMasks.add")}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div>