"use client"; import { Button } from "@app/components/ui/button"; import { Checkbox } from "@app/components/ui/checkbox"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@app/components/ui/command"; import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@app/components/ui/form"; import { Input } from "@app/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@app/components/ui/popover"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@app/components/ui/select"; import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group"; import { Label } from "@app/components/ui/label"; import { TagInput, type Tag } from "@app/components/tags/tag-input"; import { getUserDisplayName } from "@app/lib/getUserDisplayName"; import { type AlertRuleFormAction, type AlertRuleFormValues } from "@app/lib/alertRuleForm"; import { orgQueries } from "@app/lib/queries"; import { useQuery } from "@tanstack/react-query"; import { ChevronsUpDown, Plus, Trash2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useMemo, useState } from "react"; import type { Control, UseFormReturn } from "react-hook-form"; import { useFormContext, useWatch } from "react-hook-form"; import { useDebounce } from "use-debounce"; export function DropdownAddAction({ onAdd }: { onAdd: (type: AlertRuleFormAction["type"]) => void; }) { const t = useTranslations(); const [open, setOpen] = useState(false); return ( { onAdd("notify"); setOpen(false); }} > {t("alertingActionNotify")} { onAdd("webhook"); setOpen(false); }} > {t("alertingActionWebhook")} ); } function SiteMultiSelect({ orgId, value, onChange }: { orgId: string; value: number[]; onChange: (v: number[]) => void; }) { const t = useTranslations(); const [open, setOpen] = useState(false); const [q, setQ] = useState(""); const [debounced] = useDebounce(q, 150); const { data: sites = [] } = useQuery( orgQueries.sites({ orgId, query: debounced, perPage: 500 }) ); const toggle = (id: number) => { if (value.includes(id)) { onChange(value.filter((x) => x !== id)); } else { onChange([...value, id]); } }; const summary = value.length === 0 ? t("alertingSelectSites") : t("alertingSitesSelected", { count: value.length }); return ( {t("siteNotFound")} {sites.map((s) => ( toggle(s.siteId)} className="cursor-pointer" > {s.name} ))} ); } function HealthCheckMultiSelect({ orgId, value, onChange }: { orgId: string; value: number[]; onChange: (v: number[]) => void; }) { const t = useTranslations(); const [open, setOpen] = useState(false); const [q, setQ] = useState(""); const [debounced] = useDebounce(q, 150); const { data: healthChecks = [] } = useQuery( orgQueries.healthChecks({ orgId }) ); const shown = useMemo(() => { const query = debounced.trim().toLowerCase(); const base = query ? healthChecks.filter((hc) => hc.name.toLowerCase().includes(query) ) : healthChecks; // Always keep already-selected items visible even if they fall outside the search if (query && value.length > 0) { const selectedNotInBase = healthChecks.filter( (hc) => value.includes(hc.targetHealthCheckId) && !base.some( (b) => b.targetHealthCheckId === hc.targetHealthCheckId ) ); return [...selectedNotInBase, ...base]; } return base; }, [healthChecks, debounced, value]); const toggle = (id: number) => { if (value.includes(id)) { onChange(value.filter((x) => x !== id)); } else { onChange([...value, id]); } }; const summary = value.length === 0 ? t("alertingSelectHealthChecks") : t("alertingHealthChecksSelected", { count: value.length }); return ( {t("alertingHealthChecksEmpty")} {shown.map((hc) => ( toggle(hc.targetHealthCheckId) } className="cursor-pointer" > {hc.name} ))} ); } export function ActionBlock({ orgId, index, control, form, onRemove, canRemove }: { orgId: string; index: number; control: Control; form: UseFormReturn; onRemove: () => void; canRemove: boolean; }) { const t = useTranslations(); const type = form.watch(`actions.${index}.type`); return (
{canRemove && ( )} ( {t("alertingActionType")} )} /> {type === "notify" && ( )} {type === "webhook" && ( )}
); } function NotifyActionFields({ orgId, index, control, form }: { orgId: string; index: number; control: Control; form: UseFormReturn; }) { const t = useTranslations(); const [emailActiveIdx, setEmailActiveIdx] = useState(null); const [activeUsersTagIndex, setActiveUsersTagIndex] = useState< number | null >(null); const [activeRolesTagIndex, setActiveRolesTagIndex] = useState< number | null >(null); const { data: orgUsers = [] } = useQuery(orgQueries.users({ orgId })); const { data: orgRoles = [] } = useQuery(orgQueries.roles({ orgId })); const allUsers = useMemo( () => orgUsers.map((u) => ({ id: String(u.id), text: getUserDisplayName({ email: u.email, name: u.name, username: u.username }) })), [orgUsers] ); const allRoles = useMemo( () => orgRoles .map((r) => ({ id: String(r.roleId), text: r.name })) .filter((r) => r.text !== "Admin"), [orgRoles] ); const userTags = (form.watch(`actions.${index}.userTags`) ?? []) as Tag[]; const roleTags = (form.watch(`actions.${index}.roleTags`) ?? []) as Tag[]; const emailTags = (form.watch(`actions.${index}.emailTags`) ?? []) as Tag[]; return (
( {t("alertingNotifyUsers")} { const next = typeof newTags === "function" ? newTags(userTags) : newTags; form.setValue( `actions.${index}.userTags`, next as Tag[] ); }} enableAutocomplete={true} autocompleteOptions={allUsers} allowDuplicates={false} restrictTagsToAutocompleteOptions={true} sortTags={true} /> )} /> ( {t("alertingNotifyRoles")} { const next = typeof newTags === "function" ? newTags(roleTags) : newTags; form.setValue( `actions.${index}.roleTags`, next as Tag[] ); }} enableAutocomplete={true} autocompleteOptions={allRoles} allowDuplicates={false} restrictTagsToAutocompleteOptions={true} sortTags={true} /> )} /> ( {t("alertingNotifyEmails")} { const next = typeof updater === "function" ? updater(emailTags) : updater; form.setValue( `actions.${index}.emailTags`, next as Tag[] ); }} activeTagIndex={emailActiveIdx} setActiveTagIndex={setEmailActiveIdx} placeholder={t("alertingEmailPlaceholder")} size="sm" allowDuplicates={false} sortTags={true} validateTag={(tag) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(tag) } delimiterList={[",", "Enter"]} /> )} />
); } function WebhookActionFields({ index, control, form }: { index: number; control: Control; form: UseFormReturn; }) { const t = useTranslations(); return (
( URL )} /> ( {t("alertingWebhookMethod")} )} /> {/* Authentication */}

{t("httpDestAuthDescription")}

( {/* None */}

{t("httpDestAuthNoneDescription")}

{/* Bearer */}

{t("httpDestAuthBearerDescription")}

{field.value === "bearer" && ( ( )} /> )}
{/* Basic */}

{t("httpDestAuthBasicDescription")}

{field.value === "basic" && ( ( )} /> )}
{/* Custom */}

{t("httpDestAuthCustomDescription")}

{field.value === "custom" && (
( )} /> ( )} />
)}
)} />
); } function WebhookHeadersField({ index, control, form }: { index: number; control: Control; form: UseFormReturn; }) { const t = useTranslations(); const headers = form.watch(`actions.${index}.headers` as const) ?? []; return (
{t("alertingWebhookHeaders")} {headers.map((_, hi) => (
( )} /> ( )} />
))}
); } export function AlertRuleSourceFields({ orgId, control }: { orgId: string; control: Control; }) { const t = useTranslations(); const { setValue, getValues } = useFormContext(); const sourceType = useWatch({ control, name: "sourceType" }); return (
( {t("alertingSourceType")} )} /> {sourceType === "site" ? ( ( {t("alertingPickSites")} )} /> ) : ( ( {t("alertingPickHealthChecks")} )} /> )}
); } export function AlertRuleTriggerFields({ control }: { control: Control; }) { const t = useTranslations(); const sourceType = useWatch({ control, name: "sourceType" }); return ( ( {t("alertingTrigger")} )} /> ); }