Skip to content

Commit

Permalink
feat(ui): Alert Correlation Rule Badges (#1189)
Browse files Browse the repository at this point in the history
Co-authored-by: Tal <[email protected]>
  • Loading branch information
asharonbaltazar and talboren authored Jun 23, 2024
1 parent 9db23d8 commit 86a0358
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 77 deletions.
49 changes: 49 additions & 0 deletions keep-ui/app/rules/CorrelationSidebar/AlertsFoundBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Badge } from "@tremor/react";
import Image from "next/image";
import { AlertDto } from "app/alerts/models";

type AlertsFoundBadgeProps = {
alertsFound: AlertDto[];
isLoading: boolean;
};

export const AlertsFoundBadge = ({
alertsFound,
isLoading,
}: AlertsFoundBadgeProps) => {
if (alertsFound.length === 0) {
return (
<Badge className="mt-3 w-full" color="gray">
{isLoading
? "Getting your alerts..."
: "No alerts were found with this condition. Please try something else."}
</Badge>
);
}

const images = alertsFound.reduce<string[]>(
(acc, { source }) => [...new Set([...acc, ...source])],
[]
);

return (
<Badge className="mt-3 w-full" color="teal">
<span className="flex items-center">
{images.map((source, index) => (
<Image
className={`inline-block ${index == 0 ? "" : "-ml-2"}`}
key={source}
alt={source}
height={24}
width={24}
title={source}
src={`/icons/${source}-icon.png`}
/>
))}
<span className="ml-4">
{alertsFound.length} alert(s) were found matching this condition
</span>
</span>
</Badge>
);
};
106 changes: 69 additions & 37 deletions keep-ui/app/rules/CorrelationSidebar/CorrelationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
import {
// MultiSelect,
// MultiSelectItem,
Button,
MultiSelect,
MultiSelectItem,
NumberInput,
Select,
SelectItem,
TextInput,
Textarea,
} from "@tremor/react";
import { Controller, get, useFormContext } from "react-hook-form";
import { CorrelationForm as CorrelationFormType } from ".";
import { AlertDto } from "app/alerts/models";
import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline";

export const CorrelationForm = () => {
type CorrelationFormProps = {
alertsFound: AlertDto[];
isLoading: boolean;
};

export const CorrelationForm = ({
alertsFound = [],
isLoading,
}: CorrelationFormProps) => {
const { control, register, formState } =
useFormContext<CorrelationFormType>();
const { errors } = formState;

const keys = [
...alertsFound.reduce<Set<string>>((acc, alert) => {
const alertKeys = Object.keys(alert);

return new Set([...acc, ...alertKeys]);
}, new Set<string>()),
];

return (
<div className="flex flex-col gap-y-4 flex-1">
<label className="text-tremor-default font-medium text-tremor-content-strong">
Expand All @@ -30,29 +48,30 @@ export const CorrelationForm = () => {
errorMessage={get(errors, "name.message")}
/>
</label>
{/* <label className="text-tremor-default font-medium text-tremor-content-strong">
Description
<Textarea
placeholder="Type here..."
className="mt-2"
{...register("description", {
required: { message: "Description is required", value: true },
})}
error={!!get(errors, "description.message")}
errorMessage={get(errors, "description.message")}
/>
</label> */}
<fieldset>
<legend className="text-tremor-default font-medium text-tremor-content-strong">
Scan every
<legend className="text-tremor-default font-medium text-tremor-content-strong flex items-center">
Scan every{" "}
<Button
className="cursor-default ml-2"
type="button"
tooltip="Time cannot exceed 14 days"
icon={QuestionMarkCircleIcon}
size="xs"
variant="light"
color="slate"
/>
</legend>
<span className="grid grid-cols-2 mt-2 gap-x-2">
<NumberInput defaultValue={5} min={1} {...register("timeAmount")} />
<NumberInput
defaultValue={5}
min={1}
{...register("timeAmount", { validate: (value) => value > 0 })}
/>
<Controller
control={control}
name="timeUnit"
render={({ field: { value, onChange } }) => (
<Select value={value} onChange={onChange}>
<Select value={value} onValueChange={onChange}>
<SelectItem value="seconds">Seconds</SelectItem>
<SelectItem value="minutes">Minutes</SelectItem>
<SelectItem value="hours">Hours</SelectItem>
Expand All @@ -62,30 +81,43 @@ export const CorrelationForm = () => {
/>
</span>
</fieldset>
{/* <label className="text-tremor-default font-medium text-tremor-content-strong hidden">
When all condition meets set alert severity to
<Select className="mt-2" name="severity">
<SelectItem value="low">Low</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="high">High</SelectItem>
<SelectItem value="critical">Critical</SelectItem>
</Select>
</label>
<label className="text-tremor-default font-medium text-tremor-content-strong">
Select attribute(s) to group by
<div>
<label
className="flex items-center text-tremor-default font-medium text-tremor-content-strong"
htmlFor="groupedAttributes"
>
Select attribute(s) to group by{" "}
{keys.length < 1 && (
<Button
className="cursor-default ml-2"
type="button"
tooltip="You cannot calculate attributes to group by without alerts"
icon={QuestionMarkCircleIcon}
size="xs"
variant="light"
color="slate"
/>
)}
</label>
<Controller
control={control}
name="groupedAttributes"
render={({ field: { value, onChange } }) => (
<MultiSelect className="mt-2" value={value} onChange={onChange}>
<MultiSelectItem value="low">Low</MultiSelectItem>
<MultiSelectItem value="medium">Medium</MultiSelectItem>
<MultiSelectItem value="high">High</MultiSelectItem>
<MultiSelectItem value="critical">Critical</MultiSelectItem>
<MultiSelect
className="mt-2"
value={value}
onValueChange={onChange}
disabled={isLoading || !keys.length}
>
{keys.map((alertKey) => (
<MultiSelectItem key={alertKey} value={alertKey}>
{alertKey}
</MultiSelectItem>
))}
</MultiSelect>
)}
/>
</label> */}
</div>
</div>
);
};
43 changes: 29 additions & 14 deletions keep-ui/app/rules/CorrelationSidebar/CorrelationSidebarBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import { useSession } from "next-auth/react";
import { useRules } from "utils/hooks/useRules";
import { CorrelationForm as CorrelationFormType } from ".";
import { useRouter, useSearchParams } from "next/navigation";
import { useSearchAlerts } from "utils/hooks/useSearchAlerts";

const TIMEFRAME_UNITS: Record<string, (amount: number) => number> = {
seconds: (amount) => amount,
minutes: (amount) => 60 * amount,
hours: (amount) => 3600 * amount,
days: (amount) => 86400 * amount,
};
export const TIMEFRAME_UNITS = {
seconds: (amount: number) => amount,
minutes: (amount: number) => 60 * amount,
hours: (amount: number) => 3600 * amount,
days: (amount: number) => 86400 * amount,
} as const;

type CorrelationSidebarBodyProps = {
toggle: VoidFunction;
Expand All @@ -30,27 +31,35 @@ export const CorrelationSidebarBody = ({
}: CorrelationSidebarBodyProps) => {
const apiUrl = getApiURL();

const methods = useForm<CorrelationFormType>({
defaultValues: defaultValue,
mode: "onChange",
});
const timeframeInSeconds = TIMEFRAME_UNITS[methods.watch("timeUnit")](
+methods.watch("timeAmount")
);

const { mutate } = useRules();
const { data: session } = useSession();

const router = useRouter();
const searchParams = useSearchParams();
const selectedId = searchParams ? searchParams.get("id") : null;

const { data: alertsFound = [], isLoading } = useSearchAlerts({
query: methods.watch("query"),
timeframe: timeframeInSeconds,
});

const [isCalloutShown, setIsCalloutShown] = useLocalStorage(
"correlation-callout",
true
);

const methods = useForm<CorrelationFormType>({ defaultValues: defaultValue });

const onCorrelationFormSubmit: SubmitHandler<CorrelationFormType> = async (
correlationFormData
) => {
const { name, query, description, timeUnit, timeAmount } =
correlationFormData;

const timeframeInSeconds = TIMEFRAME_UNITS[timeUnit](+timeAmount);
const { name, query, description, groupedAttributes } = correlationFormData;

if (session) {
const response = await fetch(
Expand All @@ -67,6 +76,7 @@ export const CorrelationSidebarBody = ({
ruleName: name,
celQuery: formatQuery(query, "cel"),
timeframeInSeconds,
grouping_criteria: alertsFound.length ? groupedAttributes : [],
}),
}
);
Expand Down Expand Up @@ -106,9 +116,14 @@ export const CorrelationSidebarBody = ({
className="grid grid-cols-2 gap-x-10 flex-1"
onSubmit={methods.handleSubmit(onCorrelationFormSubmit)}
>
<CorrelationForm />
<CorrelationForm alertsFound={alertsFound} isLoading={isLoading} />
<CorrelationGroups />
<CorrelationSubmission toggle={toggle} />

<CorrelationSubmission
toggle={toggle}
alertsFound={alertsFound}
timeframeInSeconds={timeframeInSeconds}
/>
</form>
</FormProvider>
</div>
Expand Down
18 changes: 16 additions & 2 deletions keep-ui/app/rules/CorrelationSidebar/CorrelationSubmission.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,45 @@ import { Button } from "@tremor/react";
import { useSearchParams } from "next/navigation";
import { useFormContext } from "react-hook-form";
import { CorrelationForm } from ".";
import { AlertsFoundBadge } from "./AlertsFoundBadge";
import { AlertDto } from "app/alerts/models";

type CorrelationSubmissionProps = {
toggle: VoidFunction;
alertsFound: AlertDto[];
timeframeInSeconds: number;
};

export const CorrelationSubmission = ({
toggle,
alertsFound,
timeframeInSeconds,
}: CorrelationSubmissionProps) => {
const {
formState: { isValid },
} = useFormContext<CorrelationForm>();

const exceeds14Days = Math.floor(timeframeInSeconds / 86400) > 13;

const searchParams = useSearchParams();
const isRuleBeingEdited = searchParams ? searchParams.get("id") : null;

return (
<div className="col-span-2 flex justify-end items-end">
<div className="col-span-2 flex justify-between items-end">
<div>
{alertsFound.length > 0 && (
<AlertsFoundBadge alertsFound={alertsFound} isLoading={false} />
)}
</div>

<div className="flex items-center gap-x-4">
<Button type="button" variant="light" color="orange" onClick={toggle}>
Cancel
</Button>
<Button
className="rounded-none"
color="orange"
disabled={isValid === false}
disabled={isValid === false || exceeds14Days}
>
{isRuleBeingEdited ? "Save correlation" : "Create correlation"}
</Button>
Expand Down
Loading

0 comments on commit 86a0358

Please sign in to comment.