mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-29 01:57:41 +03:00
renaming and deletion fixes
This commit is contained in:
parent
efe5864ebc
commit
c9e320c313
@ -1647,24 +1647,14 @@ def update_trigger_embedding(
|
|||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
|
|
||||||
old = list(
|
# Check if trigger exists for upsert
|
||||||
Trigger.select(Trigger.camera, Trigger.name, Trigger.data)
|
trigger = Trigger.get_or_none(Trigger.camera == camera, Trigger.name == name)
|
||||||
.where(Trigger.camera == camera, Trigger.name == name)
|
|
||||||
.dicts()
|
|
||||||
.iterator()
|
|
||||||
)
|
|
||||||
if not old:
|
|
||||||
return JSONResponse(
|
|
||||||
content={
|
|
||||||
"success": False,
|
|
||||||
"message": f"Trigger {camera}:{name} not found",
|
|
||||||
},
|
|
||||||
status_code=404,
|
|
||||||
)
|
|
||||||
|
|
||||||
context.delete_trigger_thumbnail(camera, old[0]["data"])
|
if trigger:
|
||||||
|
# Update existing trigger
|
||||||
|
if trigger.data != body.data: # Delete old thumbnail only if data changes
|
||||||
|
context.delete_trigger_thumbnail(camera, trigger.data)
|
||||||
|
|
||||||
updated = (
|
|
||||||
Trigger.update(
|
Trigger.update(
|
||||||
data=body.data,
|
data=body.data,
|
||||||
model=request.app.frigate_config.semantic_search.model,
|
model=request.app.frigate_config.semantic_search.model,
|
||||||
@ -1672,18 +1662,19 @@ def update_trigger_embedding(
|
|||||||
threshold=body.threshold,
|
threshold=body.threshold,
|
||||||
triggering_event_id="",
|
triggering_event_id="",
|
||||||
last_triggered=None,
|
last_triggered=None,
|
||||||
)
|
).where(Trigger.camera == camera, Trigger.name == name).execute()
|
||||||
.where(Trigger.camera == camera, Trigger.name == name)
|
else:
|
||||||
.execute()
|
# Create new trigger (for rename case)
|
||||||
)
|
Trigger.create(
|
||||||
|
camera=camera,
|
||||||
if updated == 0:
|
name=name,
|
||||||
return JSONResponse(
|
type=body.type,
|
||||||
content={
|
data=body.data,
|
||||||
"success": False,
|
threshold=body.threshold,
|
||||||
"message": f"Trigger {camera}:{name} not found",
|
model=request.app.frigate_config.semantic_search.model,
|
||||||
},
|
embedding=np.array(embedding, dtype=np.float32).tobytes(),
|
||||||
status_code=404,
|
triggering_event_id="",
|
||||||
|
last_triggered=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
if body.type == "thumbnail":
|
if body.type == "thumbnail":
|
||||||
|
|||||||
@ -196,6 +196,7 @@ class WebPushClient(Communicator): # type: ignore[misc]
|
|||||||
# notification action enabled
|
# notification action enabled
|
||||||
if (
|
if (
|
||||||
not self.config.cameras[camera].notifications.enabled
|
not self.config.cameras[camera].notifications.enabled
|
||||||
|
or name not in self.config.cameras[camera].semantic_search.triggers
|
||||||
or "notification"
|
or "notification"
|
||||||
not in self.config.cameras[camera]
|
not in self.config.cameras[camera]
|
||||||
.semantic_search.triggers[name]
|
.semantic_search.triggers[name]
|
||||||
|
|||||||
@ -81,7 +81,9 @@ class SemanticTriggerProcessor(PostProcessorApi):
|
|||||||
|
|
||||||
for trigger in triggers:
|
for trigger in triggers:
|
||||||
if (
|
if (
|
||||||
not self.config.cameras[camera]
|
trigger["name"]
|
||||||
|
not in self.config.cameras[camera].semantic_search.triggers
|
||||||
|
or not self.config.cameras[camera]
|
||||||
.semantic_search.triggers[trigger["name"]]
|
.semantic_search.triggers[trigger["name"]]
|
||||||
.enabled
|
.enabled
|
||||||
):
|
):
|
||||||
|
|||||||
@ -681,7 +681,7 @@
|
|||||||
},
|
},
|
||||||
"deleteTrigger": {
|
"deleteTrigger": {
|
||||||
"title": "Delete Trigger",
|
"title": "Delete Trigger",
|
||||||
"desc": "Are you sure you want to delete the trigger <strong>{{triggerName}}</strong> from camera {{camera}}? This action cannot be undone."
|
"desc": "Are you sure you want to delete the trigger <strong>{{triggerName}}</strong>? This action cannot be undone."
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"name": {
|
"name": {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@ -40,6 +40,7 @@ type CreateTriggerDialogProps = {
|
|||||||
show: boolean;
|
show: boolean;
|
||||||
trigger: Trigger | null;
|
trigger: Trigger | null;
|
||||||
selectedCamera: string;
|
selectedCamera: string;
|
||||||
|
isLoading: boolean;
|
||||||
onCreate: (
|
onCreate: (
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
name: string,
|
name: string,
|
||||||
@ -56,12 +57,12 @@ export default function CreateTriggerDialog({
|
|||||||
show,
|
show,
|
||||||
trigger,
|
trigger,
|
||||||
selectedCamera,
|
selectedCamera,
|
||||||
|
isLoading,
|
||||||
onCreate,
|
onCreate,
|
||||||
onEdit,
|
onEdit,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: CreateTriggerDialogProps) {
|
}: CreateTriggerDialogProps) {
|
||||||
const { t } = useTranslation("views/settings");
|
const { t } = useTranslation("views/settings");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
const existingTriggerNames = useMemo(() => {
|
const existingTriggerNames = useMemo(() => {
|
||||||
@ -112,7 +113,6 @@ export default function CreateTriggerDialog({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||||
setIsLoading(true);
|
|
||||||
if (trigger) {
|
if (trigger) {
|
||||||
onEdit({ ...values });
|
onEdit({ ...values });
|
||||||
} else {
|
} else {
|
||||||
@ -125,7 +125,6 @@ export default function CreateTriggerDialog({
|
|||||||
values.actions,
|
values.actions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -9,10 +9,12 @@ import {
|
|||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
|
|
||||||
type DeleteTriggerDialogProps = {
|
type DeleteTriggerDialogProps = {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
triggerName: string;
|
triggerName: string;
|
||||||
|
isLoading: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
};
|
};
|
||||||
@ -20,6 +22,7 @@ type DeleteTriggerDialogProps = {
|
|||||||
export default function DeleteTriggerDialog({
|
export default function DeleteTriggerDialog({
|
||||||
show,
|
show,
|
||||||
triggerName,
|
triggerName,
|
||||||
|
isLoading,
|
||||||
onCancel,
|
onCancel,
|
||||||
onDelete,
|
onDelete,
|
||||||
}: DeleteTriggerDialogProps) {
|
}: DeleteTriggerDialogProps) {
|
||||||
@ -48,16 +51,25 @@ export default function DeleteTriggerDialog({
|
|||||||
aria-label={t("button.cancel", { ns: "common" })}
|
aria-label={t("button.cancel", { ns: "common" })}
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
type="button"
|
type="button"
|
||||||
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{t("button.cancel", { ns: "common" })}
|
{t("button.cancel", { ns: "common" })}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
aria-label={t("button.delete", { ns: "common" })}
|
aria-label={t("button.delete", { ns: "common" })}
|
||||||
className="flex flex-1"
|
className="flex flex-1 text-white"
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{t("button.delete", { ns: "common" })}
|
{isLoading ? (
|
||||||
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
<ActivityIndicator />
|
||||||
|
<span>{t("button.delete", { ns: "common" })}</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
t("button.delete", { ns: "common" })
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -60,7 +60,7 @@ export default function DeleteUserDialog({
|
|||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
aria-label={t("button.delete", { ns: "common" })}
|
aria-label={t("button.delete", { ns: "common" })}
|
||||||
className="flex flex-1"
|
className="flex flex-1 text-white"
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
>
|
>
|
||||||
{t("button.delete", { ns: "common" })}
|
{t("button.delete", { ns: "common" })}
|
||||||
|
|||||||
@ -476,6 +476,7 @@ function LibrarySelector({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
|
className="text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (confirmDelete) {
|
if (confirmDelete) {
|
||||||
handleDeleteFace(confirmDelete);
|
handleDeleteFace(confirmDelete);
|
||||||
|
|||||||
@ -431,6 +431,7 @@ function LibrarySelector({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
|
className="text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (confirmDelete) {
|
if (confirmDelete) {
|
||||||
handleDeleteFace(confirmDelete);
|
handleDeleteFace(confirmDelete);
|
||||||
|
|||||||
@ -169,6 +169,7 @@ export default function TriggerView({
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
setShowCreate(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[t, updateConfig, selectedCamera, setUnsavedChanges],
|
[t, updateConfig, selectedCamera, setUnsavedChanges],
|
||||||
@ -185,7 +186,6 @@ export default function TriggerView({
|
|||||||
) => {
|
) => {
|
||||||
setUnsavedChanges(true);
|
setUnsavedChanges(true);
|
||||||
saveToConfig({ enabled, name, type, data, threshold, actions }, false);
|
saveToConfig({ enabled, name, type, data, threshold, actions }, false);
|
||||||
setShowCreate(false);
|
|
||||||
},
|
},
|
||||||
[saveToConfig, setUnsavedChanges],
|
[saveToConfig, setUnsavedChanges],
|
||||||
);
|
);
|
||||||
@ -193,18 +193,40 @@ export default function TriggerView({
|
|||||||
const onEdit = useCallback(
|
const onEdit = useCallback(
|
||||||
(trigger: Trigger) => {
|
(trigger: Trigger) => {
|
||||||
setUnsavedChanges(true);
|
setUnsavedChanges(true);
|
||||||
|
setIsLoading(true);
|
||||||
if (selectedTrigger?.name && selectedTrigger.name !== trigger.name) {
|
if (selectedTrigger?.name && selectedTrigger.name !== trigger.name) {
|
||||||
// Handle rename by deleting old trigger
|
// Handle rename: delete old trigger, update config, then save new trigger
|
||||||
axios
|
axios
|
||||||
.delete(
|
.delete(
|
||||||
`/trigger/embedding/${selectedCamera}/${selectedTrigger.name}`,
|
`/trigger/embedding/${selectedCamera}/${selectedTrigger.name}`,
|
||||||
)
|
)
|
||||||
.then((embeddingResponse) => {
|
.then((embeddingResponse) => {
|
||||||
if (embeddingResponse.data.success) {
|
if (!embeddingResponse.data.success) {
|
||||||
return saveToConfig(trigger, true);
|
|
||||||
} else {
|
|
||||||
throw new Error(embeddingResponse.data.message);
|
throw new Error(embeddingResponse.data.message);
|
||||||
}
|
}
|
||||||
|
const deleteConfigBody: ConfigSetBody = {
|
||||||
|
requires_restart: 0,
|
||||||
|
config_data: {
|
||||||
|
cameras: {
|
||||||
|
[selectedCamera]: {
|
||||||
|
semantic_search: {
|
||||||
|
triggers: {
|
||||||
|
[selectedTrigger.name]: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update_topic: `config/cameras/${selectedCamera}/semantic_search`,
|
||||||
|
};
|
||||||
|
return axios.put("config/set", deleteConfigBody);
|
||||||
|
})
|
||||||
|
.then((configResponse) => {
|
||||||
|
if (configResponse.status !== 200) {
|
||||||
|
throw new Error(configResponse.statusText);
|
||||||
|
}
|
||||||
|
// Save new trigger
|
||||||
|
saveToConfig(trigger, false);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
@ -218,9 +240,9 @@ export default function TriggerView({
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Regular update without rename
|
||||||
saveToConfig(trigger, true);
|
saveToConfig(trigger, true);
|
||||||
}
|
}
|
||||||
setShowCreate(false);
|
|
||||||
setSelectedTrigger(null);
|
setSelectedTrigger(null);
|
||||||
},
|
},
|
||||||
[t, saveToConfig, selectedCamera, selectedTrigger, setUnsavedChanges],
|
[t, saveToConfig, selectedCamera, selectedTrigger, setUnsavedChanges],
|
||||||
@ -254,7 +276,6 @@ export default function TriggerView({
|
|||||||
.put("config/set", configBody)
|
.put("config/set", configBody)
|
||||||
.then((configResponse) => {
|
.then((configResponse) => {
|
||||||
if (configResponse.status === 200) {
|
if (configResponse.status === 200) {
|
||||||
setShowDelete(false);
|
|
||||||
updateConfig();
|
updateConfig();
|
||||||
toast.success(
|
toast.success(
|
||||||
t("triggers.toast.success.deleteTrigger", { name }),
|
t("triggers.toast.success.deleteTrigger", { name }),
|
||||||
@ -282,6 +303,7 @@ export default function TriggerView({
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
setShowDelete(false);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -439,7 +461,7 @@ export default function TriggerView({
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
className="h-8 px-2"
|
className="h-8 px-2 text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedTrigger(trigger);
|
setSelectedTrigger(trigger);
|
||||||
setShowDelete(true);
|
setShowDelete(true);
|
||||||
@ -472,6 +494,7 @@ export default function TriggerView({
|
|||||||
show={showCreate}
|
show={showCreate}
|
||||||
trigger={selectedTrigger}
|
trigger={selectedTrigger}
|
||||||
selectedCamera={selectedCamera}
|
selectedCamera={selectedCamera}
|
||||||
|
isLoading={isLoading}
|
||||||
onCreate={onCreate}
|
onCreate={onCreate}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
@ -483,6 +506,7 @@ export default function TriggerView({
|
|||||||
<DeleteTriggerDialog
|
<DeleteTriggerDialog
|
||||||
show={showDelete}
|
show={showDelete}
|
||||||
triggerName={selectedTrigger?.name ?? ""}
|
triggerName={selectedTrigger?.name ?? ""}
|
||||||
|
isLoading={isLoading}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setShowDelete(false);
|
setShowDelete(false);
|
||||||
setSelectedTrigger(null);
|
setSelectedTrigger(null);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user