mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-29 15:19:00 +03:00
Profiles fixes (#23306)
* add prop to disable id field * disable id field when editing profile mask/zone also, disable if the zone name already exists in required_zones or the base config is being edited and the id already exists on a profile * add backend validation to reject profile-omly masks/zones * add tests * update docs * tweak
This commit is contained in:
@@ -26,6 +26,7 @@ type NameAndIdFieldsProps<T extends FieldValues = FieldValues> = {
|
||||
placeholderName?: string;
|
||||
placeholderId?: string;
|
||||
idVisible?: boolean;
|
||||
idDisabled?: boolean;
|
||||
};
|
||||
|
||||
export default function NameAndIdFields<T extends FieldValues = FieldValues>({
|
||||
@@ -41,6 +42,7 @@ export default function NameAndIdFields<T extends FieldValues = FieldValues>({
|
||||
placeholderName,
|
||||
placeholderId,
|
||||
idVisible,
|
||||
idDisabled,
|
||||
}: NameAndIdFieldsProps<T>) {
|
||||
const { t } = useTranslation(["common"]);
|
||||
const { watch, setValue, trigger, formState } = useFormContext<T>();
|
||||
@@ -59,6 +61,9 @@ export default function NameAndIdFields<T extends FieldValues = FieldValues>({
|
||||
const effectiveProcessId = processId || defaultProcessId;
|
||||
|
||||
useEffect(() => {
|
||||
if (idDisabled) {
|
||||
return;
|
||||
}
|
||||
const subscription = watch((value, { name }) => {
|
||||
if (name === nameField) {
|
||||
hasUserTypedRef.current = true;
|
||||
@@ -68,7 +73,15 @@ export default function NameAndIdFields<T extends FieldValues = FieldValues>({
|
||||
}
|
||||
});
|
||||
return () => subscription.unsubscribe();
|
||||
}, [watch, setValue, trigger, nameField, idField, effectiveProcessId]);
|
||||
}, [
|
||||
watch,
|
||||
setValue,
|
||||
trigger,
|
||||
nameField,
|
||||
idField,
|
||||
effectiveProcessId,
|
||||
idDisabled,
|
||||
]);
|
||||
|
||||
// Auto-expand if there's an error on the ID field after user has typed
|
||||
useEffect(() => {
|
||||
@@ -123,6 +136,7 @@ export default function NameAndIdFields<T extends FieldValues = FieldValues>({
|
||||
<Input
|
||||
className="text-md"
|
||||
placeholder={placeholderId}
|
||||
disabled={idDisabled}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -258,8 +258,9 @@ export default function MotionMaskEditPane({
|
||||
},
|
||||
);
|
||||
updateConfig();
|
||||
// Only publish WS state for base config when mask has a name
|
||||
if (!editingProfile && maskName) {
|
||||
// Only publish WS state for base config when mask has a name and
|
||||
// wasn't renamed (the hook is bound to the old name).
|
||||
if (!editingProfile && maskName && !renamingMask) {
|
||||
sendMotionMaskState(enabled ? "ON" : "OFF");
|
||||
}
|
||||
} else {
|
||||
@@ -414,6 +415,7 @@ export default function MotionMaskEditPane({
|
||||
nameLabel={t("masksAndZones.motionMasks.name.title")}
|
||||
nameDescription={t("masksAndZones.motionMasks.name.description")}
|
||||
placeholderName={t("masksAndZones.motionMasks.name.placeholder")}
|
||||
idDisabled={!!editingProfile && polygon.name.length > 0}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -263,8 +263,9 @@ export default function ObjectMaskEditPane({
|
||||
},
|
||||
);
|
||||
updateConfig();
|
||||
// Only publish WS state for base config when mask has a name
|
||||
if (!editingProfile && maskName) {
|
||||
// Only publish WS state for base config when mask has a name and
|
||||
// wasn't renamed (the hook is bound to the old name).
|
||||
if (!editingProfile && maskName && !renamingMask) {
|
||||
sendObjectMaskState(enabled ? "ON" : "OFF");
|
||||
}
|
||||
} else {
|
||||
@@ -389,6 +390,7 @@ export default function ObjectMaskEditPane({
|
||||
placeholderName={t(
|
||||
"masksAndZones.objectMasks.name.placeholder",
|
||||
)}
|
||||
idDisabled={!!editingProfile && polygon.name.length > 0}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -94,6 +94,28 @@ export default function ZoneEditPane({
|
||||
const zoneName = polygon?.name || "";
|
||||
const { send: sendZoneState } = useZoneState(polygon?.camera || "", zoneName);
|
||||
|
||||
const isExistingZone = !!polygon && polygon.name.length > 0;
|
||||
|
||||
const idDisabled = useMemo(() => {
|
||||
if (!isExistingZone || !polygon) {
|
||||
return false;
|
||||
}
|
||||
if (editingProfile) {
|
||||
return true;
|
||||
}
|
||||
const cam = config?.cameras[polygon.camera];
|
||||
if (!cam) {
|
||||
return false;
|
||||
}
|
||||
const inRequiredZones =
|
||||
cam.review.alerts.required_zones.includes(polygon.name) ||
|
||||
cam.review.detections.required_zones.includes(polygon.name);
|
||||
const hasProfileOverride = Object.values(cam.profiles ?? {}).some(
|
||||
(profile) => profile?.zones && polygon.name in profile.zones,
|
||||
);
|
||||
return inRequiredZones || hasProfileOverride;
|
||||
}, [config, polygon, editingProfile, isExistingZone]);
|
||||
|
||||
const cameraConfig = useMemo(() => {
|
||||
if (polygon?.camera && config) {
|
||||
return config.cameras[polygon.camera];
|
||||
@@ -419,6 +441,7 @@ export default function ZoneEditPane({
|
||||
toast.error(t("toast.save.error.noMessage", { ns: "common" }), {
|
||||
position: "top-center",
|
||||
});
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -444,6 +467,7 @@ export default function ZoneEditPane({
|
||||
toast.error(t("toast.save.error.noMessage", { ns: "common" }), {
|
||||
position: "top-center",
|
||||
});
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -527,8 +551,9 @@ export default function ZoneEditPane({
|
||||
},
|
||||
);
|
||||
updateConfig();
|
||||
// Only publish WS state for base config when zone has a name
|
||||
if (!editingProfile && polygon?.name) {
|
||||
// Only publish WS state for base config when zone has a name and
|
||||
// wasn't renamed (the hook is bound to the old name).
|
||||
if (!editingProfile && polygon?.name && !renamingZone) {
|
||||
sendZoneState(enabled ? "ON" : "OFF");
|
||||
}
|
||||
} else {
|
||||
@@ -650,6 +675,7 @@ export default function ZoneEditPane({
|
||||
nameLabel={t("masksAndZones.zones.name.title")}
|
||||
nameDescription={t("masksAndZones.zones.name.tips")}
|
||||
placeholderName={t("masksAndZones.zones.name.inputPlaceHolder")}
|
||||
idDisabled={idDisabled}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
Reference in New Issue
Block a user