diff --git a/keep-ui/app/alerts/alert-actions.tsx b/keep-ui/app/alerts/alert-actions.tsx
index d6ca4e316..ec26b7ca4 100644
--- a/keep-ui/app/alerts/alert-actions.tsx
+++ b/keep-ui/app/alerts/alert-actions.tsx
@@ -4,6 +4,10 @@ import { getSession } from "next-auth/react";
import { getApiURL } from "utils/apiUrl";
import { AlertDto } from "./models";
import { useAlerts } from "utils/hooks/useAlerts";
+import { PlusIcon } from "@radix-ui/react-icons";
+import { toast } from "react-toastify";
+import { usePresets } from "utils/hooks/usePresets";
+import { usePathname, useRouter } from "next/navigation";
interface Props {
selectedRowIds: string[];
@@ -11,8 +15,18 @@ interface Props {
}
export default function AlertActions({ selectedRowIds, alerts }: Props) {
+ const pathname = usePathname();
+ const router = useRouter();
const { useAllAlerts } = useAlerts();
- const { mutate } = useAllAlerts();
+ const { mutate } = useAllAlerts({ revalidateOnFocus: false });
+ const { useAllPresets } = usePresets();
+ const { mutate: presetsMutator } = useAllPresets({
+ revalidateOnFocus: false,
+ });
+
+ const selectedAlerts = alerts.filter((_alert, index) =>
+ selectedRowIds.includes(index.toString())
+ );
const onDelete = async () => {
const confirmed = confirm(
@@ -23,10 +37,6 @@ export default function AlertActions({ selectedRowIds, alerts }: Props) {
const session = await getSession();
const apiUrl = getApiURL();
- const selectedAlerts = alerts.filter((_alert, index) =>
- selectedRowIds.includes(index.toString())
- );
-
for await (const alert of selectedAlerts) {
const { fingerprint } = alert;
@@ -51,6 +61,36 @@ export default function AlertActions({ selectedRowIds, alerts }: Props) {
}
};
+ async function addOrUpdatePreset() {
+ const presetName = prompt("Enter new preset name");
+ if (presetName) {
+ const distinctAlertNames = Array.from(
+ new Set(selectedAlerts.map((alert) => alert.name))
+ );
+ const options = distinctAlertNames.map((name) => {
+ return { value: `name=${name}`, label: `name=${name}` };
+ });
+ const session = await getSession();
+ const apiUrl = getApiURL();
+ const response = await fetch(`${apiUrl}/preset`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${session?.accessToken}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ name: presetName, options: options }),
+ });
+ if (response.ok) {
+ toast(`Preset ${presetName} created!`, {
+ position: "top-left",
+ type: "success",
+ });
+ presetsMutator();
+ router.replace(`${pathname}?selectedPreset=${presetName}`);
+ }
+ }
+ }
+
return (
+
);
}
diff --git a/keep-ui/app/alerts/alert-assign-ticket-modal.tsx b/keep-ui/app/alerts/alert-assign-ticket-modal.tsx
new file mode 100644
index 000000000..be2107132
--- /dev/null
+++ b/keep-ui/app/alerts/alert-assign-ticket-modal.tsx
@@ -0,0 +1,199 @@
+import React from 'react';
+import Select, { components } from 'react-select';
+import { Dialog } from '@headlessui/react';
+import { Button, TextInput } from '@tremor/react';
+import { PlusIcon } from '@heroicons/react/20/solid'
+import { useForm, Controller, SubmitHandler } from 'react-hook-form';
+import { Providers } from "./../providers/providers";
+import { useSession } from "next-auth/react";
+import { getApiURL } from 'utils/apiUrl';
+
+interface AlertAssignTicketModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ ticketingProviders: Providers; // Replace 'ProviderType' with the actual type of ticketingProviders
+ alertFingerprint: string; // Replace 'string' with the actual type of alertFingerprint
+}
+
+interface OptionType {
+ value: string;
+ label: string;
+ id: string;
+ type: string;
+ icon?: string;
+ isAddProvider?: boolean;
+}
+
+interface FormData {
+ provider: {
+ id: string;
+ value: string;
+ type: string;
+ };
+ ticket_url: string;
+}
+
+const AlertAssignTicketModal = ({ isOpen, onClose, ticketingProviders, alertFingerprint }: AlertAssignTicketModalProps) => {
+ const { handleSubmit, control, formState: { errors } } = useForm();
+ // get the token
+ const { data: session } = useSession();
+
+ const onSubmit: SubmitHandler = async (data) => {
+ try {
+ // build the formData
+ const requestData = {
+ enrichments: {
+ ticket_type: data.provider.type,
+ ticket_url: data.ticket_url,
+ ticket_provider_id: data.provider.value,
+ },
+ fingerprint: alertFingerprint,
+ };
+
+
+ const response = await fetch(`${getApiURL()}/alerts/enrich`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${session?.accessToken}`,
+ },
+ body: JSON.stringify(requestData),
+ });
+
+ if (response.ok) {
+ // Handle success
+ console.log("Ticket assigned successfully");
+ onClose();
+ } else {
+ // Handle error
+ console.error("Failed to assign ticket");
+ }
+ } catch (error) {
+ // Handle unexpected error
+ console.error("An unexpected error occurred");
+ }
+ };
+
+ const providerOptions: OptionType[] = ticketingProviders.map((provider) => ({
+ id: provider.id,
+ value: provider.id,
+ label: provider.details.name || '',
+ type: provider.type,
+ }));
+
+ const customOptions: OptionType[] = [
+ ...providerOptions,
+ {
+ value: 'add_provider',
+ label: 'Add another ticketing provider',
+ icon: 'plus',
+ isAddProvider: true,
+ id: 'add_provider',
+ type: '',
+ },
+ ];
+
+ const handleOnChange = (option: any) => {
+ if (option.value === 'add_provider') {
+ window.open('/providers?labels=ticketing', '_blank');
+ }
+ };
+
+
+ const Option = (props: any) => {
+ // Check if the option is 'add_provider'
+ const isAddProvider = props.data.isAddProvider;
+
+ return (
+
+
+ {isAddProvider ? (
+
+ ) : (
+ props.data.type &&
+ )}
+
{props.data.label}
+
+
+ );
+ };
+
+ const SingleValue = (props: any) => {
+ const { children, data } = props;
+
+ return (
+
+
+ {data.isAddProvider ? (
+
+ ) : (
+ data.type &&
+ )}
+ {children}
+
+
+ );
+ };
+
+ return (
+
+ );
+};
+
+export default AlertAssignTicketModal;
diff --git a/keep-ui/app/alerts/alert-extra-payload.tsx b/keep-ui/app/alerts/alert-extra-payload.tsx
index 339cb3459..c7fb5f847 100644
--- a/keep-ui/app/alerts/alert-extra-payload.tsx
+++ b/keep-ui/app/alerts/alert-extra-payload.tsx
@@ -49,7 +49,7 @@ export default function AlertExtraPayload({
Extra Payload
-
+
{JSON.stringify(extraPayload, null, 2)}
diff --git a/keep-ui/app/alerts/alert-menu.tsx b/keep-ui/app/alerts/alert-menu.tsx
index fccf50c49..af67a9e73 100644
--- a/keep-ui/app/alerts/alert-menu.tsx
+++ b/keep-ui/app/alerts/alert-menu.tsx
@@ -1,5 +1,5 @@
import { Menu, Portal, Transition } from "@headlessui/react";
-import { Fragment, useState } from "react";
+import { Fragment, useEffect, useRef, useState } from "react";
import { Icon } from "@tremor/react";
import {
ArchiveBoxIcon,
@@ -21,9 +21,11 @@ import { useRouter } from "next/navigation";
interface Props {
alert: AlertDto;
+ isMenuOpen: boolean;
+ setIsMenuOpen: (key: string) => void;
}
-export default function AlertMenu({ alert }: Props) {
+export default function AlertMenu({ alert, isMenuOpen, setIsMenuOpen}: Props) {
const router = useRouter();
const apiUrl = getApiURL();
@@ -40,7 +42,10 @@ export default function AlertMenu({ alert }: Props) {
const [isOpen, setIsOpen] = useState(false);
const [method, setMethod] = useState(null);
+
+
const { refs, x, y } = useFloating();
+
const alertName = alert.name;
const fingerprint = alert.fingerprint;
const alertSource = alert.source![0];
@@ -131,163 +136,168 @@ export default function AlertMenu({ alert }: Props) {
const canAssign = !alert.assignee;
+ const handleMenuToggle = () => {
+ setIsMenuOpen(alert.fingerprint);
+ };
+
+ const handleCloseMenu = () => {
+ setIsMenuOpen('');
+ }
+
return (
<>