mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-22 23:11:54 +03:00
frontend
This commit is contained in:
parent
8f2cfb0544
commit
20091ea8b9
@ -60,6 +60,7 @@ type CreateTriggerDialogProps = {
|
|||||||
data: string,
|
data: string,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
actions: TriggerAction[],
|
actions: TriggerAction[],
|
||||||
|
friendly_name: string,
|
||||||
) => void;
|
) => void;
|
||||||
onEdit: (trigger: Trigger) => void;
|
onEdit: (trigger: Trigger) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
@ -102,6 +103,7 @@ export default function CreateTriggerDialog({
|
|||||||
!existingTriggerNames.includes(value) || value === trigger?.name,
|
!existingTriggerNames.includes(value) || value === trigger?.name,
|
||||||
t("triggers.dialog.form.name.error.alreadyExists"),
|
t("triggers.dialog.form.name.error.alreadyExists"),
|
||||||
),
|
),
|
||||||
|
friendly_name: z.string().optional(),
|
||||||
type: z.enum(["thumbnail", "description"]),
|
type: z.enum(["thumbnail", "description"]),
|
||||||
data: z.string().min(1, t("triggers.dialog.form.content.error.required")),
|
data: z.string().min(1, t("triggers.dialog.form.content.error.required")),
|
||||||
threshold: z
|
threshold: z
|
||||||
@ -117,6 +119,7 @@ export default function CreateTriggerDialog({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
enabled: trigger?.enabled ?? true,
|
enabled: trigger?.enabled ?? true,
|
||||||
name: trigger?.name ?? "",
|
name: trigger?.name ?? "",
|
||||||
|
friendly_name: trigger?.friendly_name ?? "",
|
||||||
type: trigger?.type ?? "description",
|
type: trigger?.type ?? "description",
|
||||||
data: trigger?.data ?? "",
|
data: trigger?.data ?? "",
|
||||||
threshold: trigger?.threshold ?? 0.5,
|
threshold: trigger?.threshold ?? 0.5,
|
||||||
@ -135,6 +138,7 @@ export default function CreateTriggerDialog({
|
|||||||
values.data,
|
values.data,
|
||||||
values.threshold,
|
values.threshold,
|
||||||
values.actions,
|
values.actions,
|
||||||
|
values.friendly_name ?? "",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -144,6 +148,7 @@ export default function CreateTriggerDialog({
|
|||||||
form.reset({
|
form.reset({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
name: "",
|
name: "",
|
||||||
|
friendly_name: "",
|
||||||
type: "description",
|
type: "description",
|
||||||
data: "",
|
data: "",
|
||||||
threshold: 0.5,
|
threshold: 0.5,
|
||||||
@ -154,6 +159,7 @@ export default function CreateTriggerDialog({
|
|||||||
{
|
{
|
||||||
enabled: trigger.enabled,
|
enabled: trigger.enabled,
|
||||||
name: trigger.name,
|
name: trigger.name,
|
||||||
|
friendly_name: trigger.friendly_name ?? "",
|
||||||
type: trigger.type,
|
type: trigger.type,
|
||||||
data: trigger.data,
|
data: trigger.data,
|
||||||
threshold: trigger.threshold,
|
threshold: trigger.threshold,
|
||||||
@ -231,6 +237,31 @@ export default function CreateTriggerDialog({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="friendly_name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t("triggers.dialog.form.friendly_name.title")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder={t(
|
||||||
|
"triggers.dialog.form.friendly_name.placeholder",
|
||||||
|
)}
|
||||||
|
className="h-10"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t("triggers.dialog.form.friendly_name.description")}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="enabled"
|
name="enabled"
|
||||||
|
|||||||
@ -237,6 +237,7 @@ export interface CameraConfig {
|
|||||||
data: string;
|
data: string;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
actions: TriggerAction[];
|
actions: TriggerAction[];
|
||||||
|
friendly_name: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,4 +8,5 @@ export type Trigger = {
|
|||||||
data: string;
|
data: string;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
actions: TriggerAction[];
|
actions: TriggerAction[];
|
||||||
|
friendly_name?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { Toaster, toast } from "sonner";
|
import { Toaster, toast } from "sonner";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import Heading from "@/components/ui/heading";
|
import Heading from "@/components/ui/heading";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import {
|
import {
|
||||||
@ -12,7 +13,13 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { LuPlus, LuTrash, LuPencil, LuSearch } from "react-icons/lu";
|
import {
|
||||||
|
LuPlus,
|
||||||
|
LuTrash,
|
||||||
|
LuPencil,
|
||||||
|
LuSearch,
|
||||||
|
LuExternalLink,
|
||||||
|
} from "react-icons/lu";
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
import CreateTriggerDialog from "@/components/overlay/CreateTriggerDialog";
|
import CreateTriggerDialog from "@/components/overlay/CreateTriggerDialog";
|
||||||
import DeleteTriggerDialog from "@/components/overlay/DeleteTriggerDialog";
|
import DeleteTriggerDialog from "@/components/overlay/DeleteTriggerDialog";
|
||||||
@ -24,6 +31,8 @@ import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useTriggers } from "@/api/ws";
|
import { useTriggers } from "@/api/ws";
|
||||||
import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
|
import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name";
|
||||||
|
import { CiCircleAlert } from "react-icons/ci";
|
||||||
|
import { useDocDomain } from "@/hooks/use-doc-domain";
|
||||||
|
|
||||||
type ConfigSetBody = {
|
type ConfigSetBody = {
|
||||||
requires_restart: number;
|
requires_restart: number;
|
||||||
@ -39,6 +48,7 @@ type ConfigSetBody = {
|
|||||||
data: string;
|
data: string;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
actions: string[];
|
actions: string[];
|
||||||
|
friendly_name?: string;
|
||||||
}
|
}
|
||||||
| "";
|
| "";
|
||||||
};
|
};
|
||||||
@ -80,6 +90,10 @@ export default function TriggerView({
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const cameraName = useCameraFriendlyName(selectedCamera);
|
const cameraName = useCameraFriendlyName(selectedCamera);
|
||||||
|
const isSemanticSearchEnabled = config?.semantic_search?.enabled ?? false;
|
||||||
|
|
||||||
|
const { getLocaleDocUrl } = useDocDomain();
|
||||||
|
|
||||||
const triggers = useMemo(() => {
|
const triggers = useMemo(() => {
|
||||||
if (
|
if (
|
||||||
!config ||
|
!config ||
|
||||||
@ -93,6 +107,7 @@ export default function TriggerView({
|
|||||||
).map(([name, trigger]) => ({
|
).map(([name, trigger]) => ({
|
||||||
enabled: trigger.enabled,
|
enabled: trigger.enabled,
|
||||||
name,
|
name,
|
||||||
|
friendly_name: trigger.friendly_name,
|
||||||
type: trigger.type,
|
type: trigger.type,
|
||||||
data: trigger.data,
|
data: trigger.data,
|
||||||
threshold: trigger.threshold,
|
threshold: trigger.threshold,
|
||||||
@ -139,7 +154,8 @@ export default function TriggerView({
|
|||||||
const saveToConfig = useCallback(
|
const saveToConfig = useCallback(
|
||||||
(trigger: Trigger, isEdit: boolean) => {
|
(trigger: Trigger, isEdit: boolean) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const { enabled, name, type, data, threshold, actions } = trigger;
|
const { enabled, name, type, data, threshold, actions, friendly_name } =
|
||||||
|
trigger;
|
||||||
const embeddingBody: TriggerEmbeddingBody = { type, data, threshold };
|
const embeddingBody: TriggerEmbeddingBody = { type, data, threshold };
|
||||||
const embeddingUrl = isEdit
|
const embeddingUrl = isEdit
|
||||||
? `/trigger/embedding/${selectedCamera}/${name}`
|
? `/trigger/embedding/${selectedCamera}/${name}`
|
||||||
@ -162,6 +178,7 @@ export default function TriggerView({
|
|||||||
data,
|
data,
|
||||||
threshold,
|
threshold,
|
||||||
actions,
|
actions,
|
||||||
|
friendly_name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -220,9 +237,21 @@ export default function TriggerView({
|
|||||||
data: string,
|
data: string,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
actions: TriggerAction[],
|
actions: TriggerAction[],
|
||||||
|
friendly_name: string,
|
||||||
) => {
|
) => {
|
||||||
setUnsavedChanges(true);
|
setUnsavedChanges(true);
|
||||||
saveToConfig({ enabled, name, type, data, threshold, actions }, false);
|
saveToConfig(
|
||||||
|
{
|
||||||
|
enabled,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
data,
|
||||||
|
threshold,
|
||||||
|
actions,
|
||||||
|
friendly_name,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[saveToConfig, setUnsavedChanges],
|
[saveToConfig, setUnsavedChanges],
|
||||||
);
|
);
|
||||||
@ -359,7 +388,7 @@ export default function TriggerView({
|
|||||||
// for adding a trigger with event id via explore context menu
|
// for adding a trigger with event id via explore context menu
|
||||||
|
|
||||||
useSearchEffect("event_id", (eventId: string) => {
|
useSearchEffect("event_id", (eventId: string) => {
|
||||||
if (!config || isLoading) {
|
if (!config || isLoading || !isSemanticSearchEnabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
setShowCreate(true);
|
setShowCreate(true);
|
||||||
@ -386,189 +415,227 @@ export default function TriggerView({
|
|||||||
<div className="flex size-full flex-col md:flex-row">
|
<div className="flex size-full flex-col md:flex-row">
|
||||||
<Toaster position="top-center" closeButton={true} />
|
<Toaster position="top-center" closeButton={true} />
|
||||||
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
|
<div className="scrollbar-container order-last mb-10 mt-2 flex h-full w-full flex-col overflow-y-auto rounded-lg border-[1px] border-secondary-foreground bg-background_alt p-2 md:order-none md:mb-0 md:mr-2 md:mt-0">
|
||||||
<div className="mb-5 flex flex-row items-center justify-between gap-2">
|
{!isSemanticSearchEnabled ? (
|
||||||
<div className="flex flex-col items-start">
|
<div className="mb-5 flex flex-row items-center justify-between gap-2">
|
||||||
<Heading as="h3" className="my-2">
|
<div className="flex flex-col items-start">
|
||||||
{t("triggers.management.title")}
|
<Heading as="h3" className="my-2">
|
||||||
</Heading>
|
{t("triggers.management.title")}
|
||||||
<p className="text-sm text-muted-foreground">
|
</Heading>
|
||||||
{t("triggers.management.desc", {
|
<p className="mb-5 text-sm text-muted-foreground">
|
||||||
camera: cameraName,
|
{t("triggers.management.desc", {
|
||||||
})}
|
camera: cameraName,
|
||||||
</p>
|
})}
|
||||||
</div>
|
</p>
|
||||||
<Button
|
<Alert variant="destructive">
|
||||||
className="flex items-center gap-2 self-start sm:self-auto"
|
<CiCircleAlert className="size-5" />
|
||||||
aria-label={t("triggers.addTrigger")}
|
<AlertTitle>{t("triggers.semanticSearch.title")}</AlertTitle>
|
||||||
variant="default"
|
<AlertDescription>
|
||||||
onClick={() => {
|
<Trans ns="views/settings">
|
||||||
setSelectedTrigger(null);
|
triggers.semanticSearch.desc
|
||||||
setShowCreate(true);
|
</Trans>
|
||||||
}}
|
<div className="mt-3 flex items-center">
|
||||||
disabled={isLoading}
|
<Link
|
||||||
>
|
to={getLocaleDocUrl("configuration/semantic_search")}
|
||||||
<LuPlus className="size-4" />
|
target="_blank"
|
||||||
{t("triggers.addTrigger")}
|
rel="noopener noreferrer"
|
||||||
</Button>
|
className="inline"
|
||||||
</div>
|
|
||||||
<div className="mb-6 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
||||||
<div className="scrollbar-container flex-1 overflow-hidden rounded-lg border border-border bg-background_alt">
|
|
||||||
<div className="h-full overflow-auto p-0">
|
|
||||||
{triggers.length === 0 ? (
|
|
||||||
<div className="flex h-24 items-center justify-center">
|
|
||||||
<p className="text-center text-muted-foreground">
|
|
||||||
{t("triggers.table.noTriggers")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{triggers.map((trigger) => (
|
|
||||||
<div
|
|
||||||
key={trigger.name}
|
|
||||||
id={`trigger-${trigger.name}`}
|
|
||||||
className="relative flex items-center justify-between rounded-lg border border-border bg-background p-4 transition-all"
|
|
||||||
>
|
>
|
||||||
<div
|
{t("readTheDocumentation", { ns: "common" })}{" "}
|
||||||
className={cn(
|
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||||
"trigger-ring pointer-events-none absolute inset-0 z-10 size-full rounded-md outline outline-[3px] -outline-offset-[2.8px] duration-500",
|
</Link>
|
||||||
triggeredTrigger === trigger.name
|
</div>
|
||||||
? "shadow-selected outline-selected"
|
</AlertDescription>
|
||||||
: "outline-transparent duration-500",
|
</Alert>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<h3
|
|
||||||
className={cn(
|
|
||||||
"truncate text-lg font-medium",
|
|
||||||
!trigger.enabled && "opacity-60",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{trigger.name}
|
|
||||||
</h3>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"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",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<Badge
|
|
||||||
variant={
|
|
||||||
trigger.type === "thumbnail"
|
|
||||||
? "default"
|
|
||||||
: "outline"
|
|
||||||
}
|
|
||||||
className={
|
|
||||||
trigger.type === "thumbnail"
|
|
||||||
? "bg-primary/20 text-primary hover:bg-primary/30"
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t(`triggers.type.${trigger.type}`)}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
to={`/explore?event_id=${trigger_status?.triggers[trigger.name]?.triggering_event_id || ""}`}
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
!trigger_status?.triggers[trigger.name]
|
|
||||||
?.triggering_event_id && "pointer-events-none",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex flex-row items-center">
|
|
||||||
{t("triggers.table.lastTriggered")}:{" "}
|
|
||||||
{trigger_status &&
|
|
||||||
trigger_status.triggers[trigger.name]
|
|
||||||
?.last_triggered
|
|
||||||
? formatUnixTimestampToDateTime(
|
|
||||||
trigger_status.triggers[trigger.name]
|
|
||||||
?.last_triggered,
|
|
||||||
{
|
|
||||||
timezone: config.ui.timezone,
|
|
||||||
date_format:
|
|
||||||
config.ui.time_format == "24hour"
|
|
||||||
? t(
|
|
||||||
"time.formattedTimestamp2.24hour",
|
|
||||||
{
|
|
||||||
ns: "common",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: t(
|
|
||||||
"time.formattedTimestamp2.12hour",
|
|
||||||
{
|
|
||||||
ns: "common",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: "Never"}
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<LuSearch className="ml-2 size-3.5" />
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
{t("details.item.button.viewInExplore", {
|
|
||||||
ns: "views/explore",
|
|
||||||
})}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
className="h-8 w-8 p-0"
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedTrigger(trigger);
|
|
||||||
setShowCreate(true);
|
|
||||||
}}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<LuPencil className="size-3.5" />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t("triggers.table.edit")}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="destructive"
|
|
||||||
className="h-8 w-8 p-0 text-white"
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedTrigger(trigger);
|
|
||||||
setShowDelete(true);
|
|
||||||
}}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<LuTrash className="size-3.5" />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>{t("triggers.table.deleteTrigger")}</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="mb-5 flex flex-row items-center justify-between gap-2">
|
||||||
|
<div className="flex flex-col items-start">
|
||||||
|
<Heading as="h3" className="my-2">
|
||||||
|
{t("triggers.management.title")}
|
||||||
|
</Heading>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("triggers.management.desc", {
|
||||||
|
camera: cameraName,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="flex items-center gap-2 self-start sm:self-auto"
|
||||||
|
aria-label={t("triggers.addTrigger")}
|
||||||
|
variant="default"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTrigger(null);
|
||||||
|
setShowCreate(true);
|
||||||
|
}}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<LuPlus className="size-4" />
|
||||||
|
{t("triggers.addTrigger")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mb-6 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div className="scrollbar-container flex-1 overflow-hidden rounded-lg border border-border bg-background_alt">
|
||||||
|
<div className="h-full overflow-auto p-0">
|
||||||
|
{triggers.length === 0 ? (
|
||||||
|
<div className="flex h-24 items-center justify-center">
|
||||||
|
<p className="text-center text-muted-foreground">
|
||||||
|
{t("triggers.table.noTriggers")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{triggers.map((trigger) => (
|
||||||
|
<div
|
||||||
|
key={trigger.name}
|
||||||
|
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">
|
||||||
|
<h3
|
||||||
|
className={cn(
|
||||||
|
"truncate text-lg font-medium",
|
||||||
|
!trigger.enabled && "opacity-60",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{trigger.friendly_name || trigger.name}
|
||||||
|
</h3>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"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",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Badge
|
||||||
|
variant={
|
||||||
|
trigger.type === "thumbnail"
|
||||||
|
? "default"
|
||||||
|
: "outline"
|
||||||
|
}
|
||||||
|
className={
|
||||||
|
trigger.type === "thumbnail"
|
||||||
|
? "bg-primary/20 text-primary hover:bg-primary/30"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t(`triggers.type.${trigger.type}`)}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
to={`/explore?event_id=${trigger_status?.triggers[trigger.name]?.triggering_event_id || ""}`}
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
!trigger_status?.triggers[trigger.name]
|
||||||
|
?.triggering_event_id &&
|
||||||
|
"pointer-events-none",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
{t("triggers.table.lastTriggered")}:{" "}
|
||||||
|
{trigger_status &&
|
||||||
|
trigger_status.triggers[trigger.name]
|
||||||
|
?.last_triggered
|
||||||
|
? formatUnixTimestampToDateTime(
|
||||||
|
trigger_status.triggers[trigger.name]
|
||||||
|
?.last_triggered,
|
||||||
|
{
|
||||||
|
timezone: config.ui.timezone,
|
||||||
|
date_format:
|
||||||
|
config.ui.time_format == "24hour"
|
||||||
|
? t(
|
||||||
|
"time.formattedTimestamp2.24hour",
|
||||||
|
{
|
||||||
|
ns: "common",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
"time.formattedTimestamp2.12hour",
|
||||||
|
{
|
||||||
|
ns: "common",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: "Never"}
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<LuSearch className="ml-2 size-3.5" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{t("details.item.button.viewInExplore", {
|
||||||
|
ns: "views/explore",
|
||||||
|
})}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTrigger(trigger);
|
||||||
|
setShowCreate(true);
|
||||||
|
}}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<LuPencil className="size-3.5" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t("triggers.table.edit")}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="destructive"
|
||||||
|
className="h-8 w-8 p-0 text-white"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTrigger(trigger);
|
||||||
|
setShowDelete(true);
|
||||||
|
}}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<LuTrash className="size-3.5" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{t("triggers.table.deleteTrigger")}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<CreateTriggerDialog
|
<CreateTriggerDialog
|
||||||
show={showCreate}
|
show={showCreate}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user