diff --git a/keep-ui/app/loading.tsx b/keep-ui/app/loading.tsx index 93f2097785..950ca419ad 100644 --- a/keep-ui/app/loading.tsx +++ b/keep-ui/app/loading.tsx @@ -1,22 +1,27 @@ import { Subtitle, Title } from "@tremor/react"; import Image from "next/image"; +import { clsx } from "clsx"; export default function Loading({ includeMinHeight = true, slowLoading = false, loadingText = "Just a second, getting your data 🚨", - extraLoadingText = "" + extraLoadingText = "", + className, }: { includeMinHeight?: boolean; slowLoading?: boolean; loadingText?: string; extraLoadingText?: string; -}) { + className?: string; +}) { return (
{loadingText} - - {extraLoadingText && ( - {extraLoadingText} - )} - + + {extraLoadingText && {extraLoadingText}} + {slowLoading && ( This is taking a bit longer then usual, please wait... diff --git a/keep-ui/app/rules/AIGenRules.tsx b/keep-ui/app/rules/AIGenRules.tsx index 3ba436e11c..abf10d9bd3 100644 --- a/keep-ui/app/rules/AIGenRules.tsx +++ b/keep-ui/app/rules/AIGenRules.tsx @@ -1,5 +1,13 @@ -import React, { useState, useEffect } from 'react'; -import { Icon, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "@tremor/react"; +import React, { useState, useEffect } from "react"; +import { + Icon, + Table, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, +} from "@tremor/react"; import { createColumnHelper, getCoreRowModel, @@ -8,18 +16,31 @@ import { getSortedRowModel, SortingState, flexRender, - Header + Header, } from "@tanstack/react-table"; -import { useRulePusherUpdates, AIGeneratedRule, useGenRules } from "utils/hooks/useRules"; -import { FaArrowDown, FaArrowRight, FaArrowUp, FaPlus, FaSpinner, FaSync } from "react-icons/fa"; -import { InformationCircleIcon, ExclamationTriangleIcon, QuestionMarkCircleIcon } from "@heroicons/react/24/solid"; +import { + useRulePusherUpdates, + AIGeneratedRule, + useGenRules, +} from "utils/hooks/useRules"; +import { + FaArrowDown, + FaArrowRight, + FaArrowUp, + FaPlus, + FaSpinner, + FaSync, +} from "react-icons/fa"; +import { + InformationCircleIcon, + ExclamationTriangleIcon, + QuestionMarkCircleIcon, +} from "@heroicons/react/24/solid"; import { useSession } from "next-auth/react"; import { getApiURL } from "utils/apiUrl"; import useSWR, { mutate } from "swr"; import Loading from "app/loading"; - - const columnHelper = createColumnHelper(); interface SortableHeaderCellProps { @@ -56,14 +77,16 @@ const SortableHeaderCell: React.FC = ({ column.getNextSortingOrder() === "asc" ? "Sort ascending" : column.getNextSortingOrder() === "desc" - ? "Sort descending" - : "Clear sort" + ? "Sort descending" + : "Clear sort" + } + icon={ + column.getIsSorted() + ? column.getIsSorted() === "asc" + ? FaArrowDown + : FaArrowUp + : FaArrowRight } - icon={column.getIsSorted() ? ( - column.getIsSorted() === "asc" ? FaArrowDown : FaArrowUp - ) : ( - FaArrowRight - )} /> )} @@ -73,28 +96,32 @@ const SortableHeaderCell: React.FC = ({ }; export const AIGenRules: React.FC = () => { - const {triggerGenRules } = useGenRules(); + const { triggerGenRules } = useGenRules(); const { data: session } = useSession(); const [sorting, setSorting] = React.useState([]); const [isLoadingRules, setIsLoadingRules] = useState(true); const [generatedRules, setGeneratedRules] = useState([]); - const [loadingRows, setLoadingRows] = useState<{ [key: string]: boolean }>({}); - const [successRows, setSuccessRows] = useState<{ [key: string]: boolean }>({}); + const [loadingRows, setLoadingRows] = useState<{ [key: string]: boolean }>( + {} + ); + const [successRows, setSuccessRows] = useState<{ [key: string]: boolean }>( + {} + ); const mutateAIGeneratedRules = (rules: AIGeneratedRule[]) => { setGeneratedRules(rules); setIsLoadingRules(false); }; - const { data:serverGenRules } = useRulePusherUpdates(); + const { data: serverGenRules } = useRulePusherUpdates(); useEffect(() => { - if (Array.isArray(serverGenRules) && (0 === serverGenRules.length)) { + if (Array.isArray(serverGenRules) && 0 === serverGenRules.length) { return; } - + mutateAIGeneratedRules(serverGenRules); }, [serverGenRules]); @@ -108,17 +135,20 @@ export const AIGenRules: React.FC = () => { setLoadingRows((prev) => ({ ...prev, [ruleKey]: true })); try { const temp = { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${session?.accessToken}`, + "Content-Type": "application/json", + Authorization: `Bearer ${session?.accessToken}`, }, body: JSON.stringify({ ruleName: rule.ShortRuleName, - sqlQuery: { sql: '{new-version-not-adding-this}', params: ['no-params'] }, + sqlQuery: { + sql: "{new-version-not-adding-this}", + params: ["no-params"], + }, celQuery: rule.CELRule, timeframeInSeconds: rule.Timeframe, - timeUnit: 'minutes', + timeUnit: "minutes", groupingCriteria: rule.GroupBy, groupDescription: rule.ChainOfThought, requireApprove: false, @@ -133,10 +163,10 @@ export const AIGenRules: React.FC = () => { mutate(`${apiUrl}/rules`); // Refresh the rules list } else { const result = await response.json(); - console.error('Failed to add rule:', result); + console.error("Failed to add rule:", result); } } catch (error) { - console.error('Error adding rule:', error); + console.error("Error adding rule:", error); } finally { setLoadingRows((prev) => { const newLoadingRows = { ...prev }; @@ -146,83 +176,87 @@ export const AIGenRules: React.FC = () => { } }; - const columns = React.useMemo(() => [ - columnHelper.accessor("ShortRuleName", { - header: "Short Rule Name", - }), - columnHelper.accessor("Score", { - header: "Score", - }), - columnHelper.accessor("CELRule", { - header: "CEL Rule", - cell: (info) =>
{info.getValue()}
, - }), - columnHelper.accessor("Timeframe", { - header: "Timeframe", - }), - columnHelper.accessor("GroupBy", { - header: "Group By", - cell: (info) => info.getValue().join(", "), - }), - columnHelper.accessor("ChainOfThought", { - header: "Explanations", - cell: (info) => { - const rule = info.row.original; - return ( -
- - - -
- ); - }, - }), - columnHelper.display({ - id: 'add', - header: 'Add', - cell: (info) => { - const rule = info.row.original; - const ruleKey = rule.ShortRuleName; - return ( -
- {loadingRows[ruleKey] ? ( - - ) : successRows[ruleKey] ? ( -
Added!
- ) : ( - - )} -
- ); - }, - }), - ] as ColumnDef[], [loadingRows, successRows]); + const columns = React.useMemo( + () => + [ + columnHelper.accessor("ShortRuleName", { + header: "Short Rule Name", + }), + columnHelper.accessor("Score", { + header: "Score", + }), + columnHelper.accessor("CELRule", { + header: "CEL Rule", + cell: (info) =>
{info.getValue()}
, + }), + columnHelper.accessor("Timeframe", { + header: "Timeframe", + }), + columnHelper.accessor("GroupBy", { + header: "Group By", + cell: (info) => info.getValue().join(", "), + }), + columnHelper.accessor("ChainOfThought", { + header: "Explanations", + cell: (info) => { + const rule = info.row.original; + return ( +
+ + + +
+ ); + }, + }), + columnHelper.display({ + id: "add", + header: "Add", + cell: (info) => { + const rule = info.row.original; + const ruleKey = rule.ShortRuleName; + return ( +
+ {loadingRows[ruleKey] ? ( + + ) : successRows[ruleKey] ? ( +
Added!
+ ) : ( + + )} +
+ ); + }, + }), + ] as ColumnDef[], + [loadingRows, successRows] + ); const table = useReactTable({ columns, @@ -236,21 +270,29 @@ export const AIGenRules: React.FC = () => { }); if (isLoadingRules) { - return ; + return ( + + ); } if (generatedRules.error) { return (
-

{generatedRules.error}

+

+ {generatedRules.error} +

Please try again later

); } - return (
@@ -260,7 +302,9 @@ export const AIGenRules: React.FC = () => { > Generate more rules -

AI Generated Rules

+

+ AI Generated Rules +

{generatedRules.summery}

@@ -271,10 +315,7 @@ export const AIGenRules: React.FC = () => { key={headerGroup.id} > {headerGroup.headers.map((header) => ( - + {flexRender( header.column.columnDef.header, header.getContext() @@ -292,8 +333,8 @@ export const AIGenRules: React.FC = () => { @@ -312,5 +353,3 @@ export const AIGenRules: React.FC = () => { }; export default AIGenRules; - - diff --git a/keep-ui/app/rules/CorrelationPlaceholder.tsx b/keep-ui/app/rules/CorrelationPlaceholder.tsx index 81fdfa4bd7..59f8a3ce6b 100644 --- a/keep-ui/app/rules/CorrelationPlaceholder.tsx +++ b/keep-ui/app/rules/CorrelationPlaceholder.tsx @@ -1,31 +1,17 @@ -import { Fragment, useState } from "react"; -import { Button, Card, Subtitle, Title } from "@tremor/react"; -import { CorrelationSidebar } from "./CorrelationSidebar"; +import { Card, Subtitle, Title } from "@tremor/react"; import { PlaceholderSankey } from "./PlaceholderSankey"; export const CorrelationPlaceholder = () => { - const [isSidebarOpen, setIsSidebarOpen] = useState(false); - - const onCorrelationClick = () => { - setIsSidebarOpen(true); - }; - return ( - - -
- No Correlations Yet - - Start building correlation and get all relevant alerts in one - dedicated place - -
- -
- setIsSidebarOpen(!isSidebarOpen)} - /> -
+ +
+ No Correlations Yet + + Start building correlation and get all relevant alerts in one + dedicated place + +
+ +
); }; diff --git a/keep-ui/app/rules/CorrelationTable.tsx b/keep-ui/app/rules/CorrelationTable.tsx index 027aa0fa02..c3fd1ec3bb 100644 --- a/keep-ui/app/rules/CorrelationTable.tsx +++ b/keep-ui/app/rules/CorrelationTable.tsx @@ -1,7 +1,8 @@ import { Badge, Button, - Card, Icon, + Card, + Icon, Subtitle, Table, TableBody, @@ -28,24 +29,22 @@ import { } from "@tanstack/react-table"; import { DefaultRuleGroupType, parseCEL } from "react-querybuilder"; import { useRouter, useSearchParams } from "next/navigation"; -import { FormattedQueryCell } from "./FormattedQueryCell"; import { DeleteRuleCell } from "./CorrelationSidebar/DeleteRule"; -import {PlusIcon} from "@radix-ui/react-icons"; +import { PlusIcon } from "@radix-ui/react-icons"; import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react"; import { BoltIcon, SparklesIcon } from "@heroicons/react/24/outline"; -import { AIGenRules } from './AIGenRules'; // Add this import at the top of the file +import { AIGenRules } from "./AIGenRules"; import { FaArrowDown, FaArrowRight, FaArrowUp } from "react-icons/fa"; -import { Header } from "@tanstack/react-table"; // Ensure this import is present - +import { Header } from "@tanstack/react-table"; +import "./CorrelationTabs.css"; import { CorrelationPlaceholder } from "./CorrelationPlaceholder"; - -const TIMEFRAME_UNITS_FROM_SECONDS= { +const TIMEFRAME_UNITS_FROM_SECONDS = { seconds: (amount: number) => amount, minutes: (amount: number) => amount / 60, hours: (amount: number) => amount / 3600, - days: (amount: number) => amount / 86400, + days: (amount: number) => amount / 86400, } as const; const columnHelper = createColumnHelper(); @@ -87,14 +86,16 @@ const SortableHeaderCell: React.FC = ({ column.getNextSortingOrder() === "asc" ? "Sort ascending" : column.getNextSortingOrder() === "desc" - ? "Sort descending" - : "Clear sort" + ? "Sort descending" + : "Clear sort" + } + icon={ + column.getIsSorted() + ? column.getIsSorted() === "asc" + ? FaArrowDown + : FaArrowUp + : FaArrowRight } - icon={column.getIsSorted() ? ( - column.getIsSorted() === "asc" ? FaArrowDown : FaArrowUp - ) : ( - FaArrowRight - )} /> )} @@ -131,7 +132,9 @@ export const CorrelationTable = ({ rules }: CorrelationTableProps) => { return { name: selectedRule.name, description: selectedRule.group_description ?? "", - timeAmount: TIMEFRAME_UNITS_FROM_SECONDS[timeunit](selectedRule.timeframe), + timeAmount: TIMEFRAME_UNITS_FROM_SECONDS[timeunit]( + selectedRule.timeframe + ), timeUnit: timeunit, groupedAttributes: selectedRule.grouping_criteria, requireApprove: selectedRule.require_approve, @@ -176,27 +179,26 @@ export const CorrelationTable = ({ rules }: CorrelationTableProps) => { }), columnHelper.accessor("grouping_criteria", { header: "Grouped by", - cell: (context) => ( - context.getValue().map((group, index) => + cell: (context) => + context.getValue().map((group, index) => ( <> - {group} + + {group} + {context.getValue().length !== index + 1 && ( - - )} + + )} - ) - ), + )), }), columnHelper.accessor("incidents", { header: "Incidents", - cell: (context) => ( - context.getValue() - ), + cell: (context) => context.getValue(), }), columnHelper.display({ - id: 'delete', + id: "delete", header: "", - cell: (context) => (), + cell: (context) => , enableSorting: false, }), ], @@ -229,8 +231,8 @@ export const CorrelationTable = ({ rules }: CorrelationTableProps) => { Create Correlation - - + + setSelectedTab("existing")}> Existing Correlations @@ -246,56 +248,53 @@ export const CorrelationTable = ({ rules }: CorrelationTableProps) => { - {rules.length === 0 ? ( - - ) : ( - - -
- - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - router.push(`?id=${row.original.id}`)} - > - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ))} - + {rules.length === 0 ? ( + + ) : ( + +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + router.push(`?id=${row.original.id}`)} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + ))} +
- )} - + )} + - -
- {isAIGenRulesLoaded ? : null} -
+ + {isAIGenRulesLoaded ? : null}
diff --git a/keep-ui/app/rules/CorrelationTabs.css b/keep-ui/app/rules/CorrelationTabs.css new file mode 100644 index 0000000000..94440d400a --- /dev/null +++ b/keep-ui/app/rules/CorrelationTabs.css @@ -0,0 +1,5 @@ +.Correlation-Tabs { + &.tremor-TabGroup-root, .tremor-TabPanels-root, .tremor-TabPanel-root:not(.hidden) { + @apply flex flex-col flex-1; + } +}