mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-29 10:07:41 +03:00
highlight entry in UI when triggered
This commit is contained in:
parent
e0c70a465f
commit
daccbd9c2b
@ -660,7 +660,8 @@
|
|||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"noTriggers": "No triggers configured for this camera.",
|
"noTriggers": "No triggers configured for this camera.",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"deleteTrigger": "Delete Trigger"
|
"deleteTrigger": "Delete Trigger",
|
||||||
|
"lastTriggered": "Last triggered"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"thumbnail": "Thumbnail",
|
"thumbnail": "Thumbnail",
|
||||||
@ -685,7 +686,7 @@
|
|||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"name": {
|
"name": {
|
||||||
"title": "Trigger Name",
|
"title": "Name",
|
||||||
"placeholder": "Enter trigger name",
|
"placeholder": "Enter trigger name",
|
||||||
"error": {
|
"error": {
|
||||||
"minLength": "Name must be at least 2 characters long.",
|
"minLength": "Name must be at least 2 characters long.",
|
||||||
@ -697,7 +698,7 @@
|
|||||||
"description": "Enable or disable this trigger"
|
"description": "Enable or disable this trigger"
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"title": "Trigger Type",
|
"title": "Type",
|
||||||
"placeholder": "Select trigger type"
|
"placeholder": "Select trigger type"
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
ModelState,
|
ModelState,
|
||||||
ToggleableSetting,
|
ToggleableSetting,
|
||||||
TrackedObjectUpdateReturnType,
|
TrackedObjectUpdateReturnType,
|
||||||
|
TriggerStatus,
|
||||||
} from "@/types/ws";
|
} from "@/types/ws";
|
||||||
import { FrigateStats } from "@/types/stats";
|
import { FrigateStats } from "@/types/stats";
|
||||||
import { createContainer } from "react-tracked";
|
import { createContainer } from "react-tracked";
|
||||||
@ -572,3 +573,13 @@ export function useNotificationTest(): {
|
|||||||
} = useWs("notification_test", "notification_test");
|
} = useWs("notification_test", "notification_test");
|
||||||
return { payload: payload as string, send };
|
return { payload: payload as string, send };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useTriggers(): { payload: TriggerStatus } {
|
||||||
|
const {
|
||||||
|
value: { payload },
|
||||||
|
} = useWs("triggers", "");
|
||||||
|
const parsed = payload
|
||||||
|
? JSON.parse(payload as string)
|
||||||
|
: { name: "", camera: "", event_id: "", type: "", score: 0 };
|
||||||
|
return { payload: useDeepMemo(parsed) };
|
||||||
|
}
|
||||||
|
|||||||
@ -105,3 +105,11 @@ export type TrackedObjectUpdateReturnType = {
|
|||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
text?: string;
|
text?: string;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
|
export type TriggerStatus = {
|
||||||
|
name: string;
|
||||||
|
camera: string;
|
||||||
|
event_id: string;
|
||||||
|
type: string;
|
||||||
|
score: number;
|
||||||
|
};
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { useSearchEffect } from "@/hooks/use-overlay-state";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { useTriggers } from "@/api/ws";
|
||||||
|
|
||||||
type ConfigSetBody = {
|
type ConfigSetBody = {
|
||||||
requires_restart: number;
|
requires_restart: number;
|
||||||
@ -65,10 +66,16 @@ export default function TriggerView({
|
|||||||
const { t } = useTranslation("views/settings");
|
const { t } = useTranslation("views/settings");
|
||||||
const { data: config, mutate: updateConfig } =
|
const { data: config, mutate: updateConfig } =
|
||||||
useSWR<FrigateConfig>("config");
|
useSWR<FrigateConfig>("config");
|
||||||
const { data: trigger_status } = useSWR(`/triggers/status/${selectedCamera}`);
|
const { data: trigger_status, mutate } = useSWR(
|
||||||
|
`/triggers/status/${selectedCamera}`,
|
||||||
|
{
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
const [showCreate, setShowCreate] = useState(false);
|
const [showCreate, setShowCreate] = useState(false);
|
||||||
const [showDelete, setShowDelete] = useState(false);
|
const [showDelete, setShowDelete] = useState(false);
|
||||||
const [selectedTrigger, setSelectedTrigger] = useState<Trigger | null>(null);
|
const [selectedTrigger, setSelectedTrigger] = useState<Trigger | null>(null);
|
||||||
|
const [triggeredTrigger, setTriggeredTrigger] = useState<string>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const triggers = useMemo(() => {
|
const triggers = useMemo(() => {
|
||||||
@ -91,6 +98,38 @@ export default function TriggerView({
|
|||||||
}));
|
}));
|
||||||
}, [config, selectedCamera]);
|
}, [config, selectedCamera]);
|
||||||
|
|
||||||
|
// watch websocket for updates
|
||||||
|
const { payload: triggers_status_ws } = useTriggers();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!triggers_status_ws) return;
|
||||||
|
|
||||||
|
mutate();
|
||||||
|
|
||||||
|
setTriggeredTrigger(triggers_status_ws.name);
|
||||||
|
const target = document.querySelector(
|
||||||
|
`#trigger-${triggers_status_ws.name}`,
|
||||||
|
);
|
||||||
|
if (target) {
|
||||||
|
target.scrollIntoView({
|
||||||
|
block: "center",
|
||||||
|
behavior: "smooth",
|
||||||
|
inline: "nearest",
|
||||||
|
});
|
||||||
|
const ring = target.querySelector(".trigger-ring");
|
||||||
|
if (ring) {
|
||||||
|
ring.classList.add(`outline-selected`);
|
||||||
|
ring.classList.remove("outline-transparent");
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
ring.classList.remove(`outline-selected`);
|
||||||
|
ring.classList.add("outline-transparent");
|
||||||
|
}, 3000);
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [triggers_status_ws, selectedCamera, mutate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = t("triggers.documentTitle");
|
document.title = t("triggers.documentTitle");
|
||||||
}, [t]);
|
}, [t]);
|
||||||
@ -382,8 +421,17 @@ export default function TriggerView({
|
|||||||
{triggers.map((trigger) => (
|
{triggers.map((trigger) => (
|
||||||
<div
|
<div
|
||||||
key={trigger.name}
|
key={trigger.name}
|
||||||
className="flex items-center justify-between rounded-lg border border-border bg-background p-4 transition-opacity"
|
id={`trigger-${trigger.name}`}
|
||||||
|
className="relative flex items-center justify-between rounded-lg border border-border bg-background p-4 transition-all"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"trigger-ring pointer-events-none absolute inset-0 z-10 size-full rounded-md outline outline-[3px] -outline-offset-[2.8px] duration-500",
|
||||||
|
triggeredTrigger === trigger.name
|
||||||
|
? "shadow-selected outline-selected"
|
||||||
|
: "outline-transparent duration-500",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h3
|
<h3
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -395,7 +443,7 @@ export default function TriggerView({
|
|||||||
</h3>
|
</h3>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-1 flex flex-col gap-0.5 text-sm text-muted-foreground md:flex-row md:items-center md:gap-3",
|
"mt-1 flex flex-col gap-1 text-sm text-muted-foreground md:flex-row md:items-center md:gap-3",
|
||||||
!trigger.enabled && "opacity-60",
|
!trigger.enabled && "opacity-60",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@ -416,10 +464,6 @@ export default function TriggerView({
|
|||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span>{trigger.threshold.toFixed(2)} threshold</span>
|
|
||||||
|
|
||||||
<span>{trigger.actions.length} actions</span>
|
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={`/explore?event_id=${trigger_status?.triggers[trigger.name]?.triggering_event_id || ""}`}
|
to={`/explore?event_id=${trigger_status?.triggers[trigger.name]?.triggering_event_id || ""}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -429,8 +473,8 @@ export default function TriggerView({
|
|||||||
"pointer-events-none opacity-60",
|
"pointer-events-none opacity-60",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-center">
|
<div className="flex flex-row items-center">
|
||||||
Last:{" "}
|
{t("triggers.table.lastTriggered")}{" "}
|
||||||
{trigger_status &&
|
{trigger_status &&
|
||||||
trigger_status.triggers[trigger.name]
|
trigger_status.triggers[trigger.name]
|
||||||
?.last_triggered
|
?.last_triggered
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user